opaque_browser_frame_view.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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/opaque_browser_frame_view.h" 6 7#include <algorithm> 8#include <string> 9 10#include "base/command_line.h" 11#include "base/compiler_specific.h" 12#include "base/prefs/pref_service.h" 13#include "base/strings/utf_string_conversions.h" 14#include "chrome/browser/chrome_notification_types.h" 15#include "chrome/browser/themes/theme_properties.h" 16#include "chrome/browser/ui/views/avatar_label.h" 17#include "chrome/browser/ui/views/avatar_menu_button.h" 18#include "chrome/browser/ui/views/frame/browser_frame.h" 19#include "chrome/browser/ui/views/frame/browser_view.h" 20#include "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h" 21#include "chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.h" 22#include "chrome/browser/ui/views/tab_icon_view.h" 23#include "chrome/browser/ui/views/tabs/tab_strip.h" 24#include "chrome/browser/ui/views/toolbar_view.h" 25#include "chrome/common/chrome_switches.h" 26#include "chrome/common/pref_names.h" 27#include "content/public/browser/notification_service.h" 28#include "content/public/browser/web_contents.h" 29#include "grit/chromium_strings.h" 30#include "grit/generated_resources.h" 31#include "grit/theme_resources.h" 32#include "grit/ui_resources.h" 33#include "ui/base/accessibility/accessible_view_state.h" 34#include "ui/base/hit_test.h" 35#include "ui/base/l10n/l10n_util.h" 36#include "ui/base/resource/resource_bundle.h" 37#include "ui/base/theme_provider.h" 38#include "ui/gfx/canvas.h" 39#include "ui/gfx/font.h" 40#include "ui/gfx/image/image.h" 41#include "ui/gfx/image/image_skia.h" 42#include "ui/gfx/path.h" 43#include "ui/views/controls/button/image_button.h" 44#include "ui/views/controls/image_view.h" 45#include "ui/views/controls/label.h" 46#include "ui/views/layout/layout_constants.h" 47#include "ui/views/widget/root_view.h" 48#include "ui/views/window/frame_background.h" 49#include "ui/views/window/window_shape.h" 50 51using content::WebContents; 52 53namespace { 54 55// While resize areas on Windows are normally the same size as the window 56// borders, our top area is shrunk by 1 px to make it easier to move the window 57// around with our thinner top grabbable strip. (Incidentally, our side and 58// bottom resize areas don't match the frame border thickness either -- they 59// span the whole nonclient area, so there's no "dead zone" for the mouse.) 60const int kTopResizeAdjust = 1; 61 62// In the window corners, the resize areas don't actually expand bigger, but the 63// 16 px at the end of each edge triggers diagonal resizing. 64const int kResizeAreaCornerSize = 16; 65 66// The content left/right images have a shadow built into them. 67const int kContentEdgeShadowThickness = 2; 68 69// The icon never shrinks below 16 px on a side. 70const int kIconMinimumSize = 16; 71 72// The top 3 px of the tabstrip is shadow; in maximized mode we push this off 73// the top of the screen so the tabs appear flush against the screen edge. 74const int kTabstripTopShadowThickness = 3; 75 76// Converts |bounds| from |src|'s coordinate system to |dst|, and checks if 77// |pt| is contained within. 78bool ConvertedContainsCheck(gfx::Rect bounds, const views::View* src, 79 const views::View* dst, const gfx::Point& pt) { 80 DCHECK(src); 81 DCHECK(dst); 82 gfx::Point origin(bounds.origin()); 83 views::View::ConvertPointToTarget(src, dst, &origin); 84 bounds.set_origin(origin); 85 return bounds.Contains(pt); 86} 87 88} // namespace 89 90/////////////////////////////////////////////////////////////////////////////// 91// OpaqueBrowserFrameView, public: 92 93OpaqueBrowserFrameView::OpaqueBrowserFrameView(BrowserFrame* frame, 94 BrowserView* browser_view) 95 : BrowserNonClientFrameView(frame, browser_view), 96 layout_(new OpaqueBrowserFrameViewLayout(this)), 97 minimize_button_(NULL), 98 maximize_button_(NULL), 99 restore_button_(NULL), 100 close_button_(NULL), 101 window_icon_(NULL), 102 window_title_(NULL), 103 frame_background_(new views::FrameBackground()) { 104 SetLayoutManager(layout_); 105 106 if (OpaqueBrowserFrameViewLayout::ShouldAddDefaultCaptionButtons()) { 107 minimize_button_ = InitWindowCaptionButton(IDR_MINIMIZE, 108 IDR_MINIMIZE_H, 109 IDR_MINIMIZE_P, 110 IDR_MINIMIZE_BUTTON_MASK, 111 IDS_ACCNAME_MINIMIZE, 112 VIEW_ID_MINIMIZE_BUTTON); 113 maximize_button_ = InitWindowCaptionButton(IDR_MAXIMIZE, 114 IDR_MAXIMIZE_H, 115 IDR_MAXIMIZE_P, 116 IDR_MAXIMIZE_BUTTON_MASK, 117 IDS_ACCNAME_MAXIMIZE, 118 VIEW_ID_MAXIMIZE_BUTTON); 119 restore_button_ = InitWindowCaptionButton(IDR_RESTORE, 120 IDR_RESTORE_H, 121 IDR_RESTORE_P, 122 IDR_RESTORE_BUTTON_MASK, 123 IDS_ACCNAME_RESTORE, 124 VIEW_ID_RESTORE_BUTTON); 125 close_button_ = InitWindowCaptionButton(IDR_CLOSE, 126 IDR_CLOSE_H, 127 IDR_CLOSE_P, 128 IDR_CLOSE_BUTTON_MASK, 129 IDS_ACCNAME_CLOSE, 130 VIEW_ID_CLOSE_BUTTON); 131 } 132 133 // Initializing the TabIconView is expensive, so only do it if we need to. 134 if (browser_view->ShouldShowWindowIcon()) { 135 window_icon_ = new TabIconView(this); 136 window_icon_->set_is_light(true); 137 window_icon_->set_id(VIEW_ID_WINDOW_ICON); 138 AddChildView(window_icon_); 139 window_icon_->Update(); 140 } 141 142 window_title_ = new views::Label(browser_view->GetWindowTitle(), 143 BrowserFrame::GetTitleFont()); 144 window_title_->SetVisible(browser_view->ShouldShowWindowTitle()); 145 window_title_->SetEnabledColor(SK_ColorWHITE); 146 // TODO(msw): Use a transparent background color as a workaround to use the 147 // gfx::Canvas::NO_SUBPIXEL_RENDERING flag and avoid some visual artifacts. 148 window_title_->SetBackgroundColor(0x00000000); 149 window_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 150 window_title_->set_id(VIEW_ID_WINDOW_TITLE); 151 AddChildView(window_title_); 152 153 UpdateAvatarInfo(); 154 if (!browser_view->IsOffTheRecord()) { 155 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, 156 content::NotificationService::AllSources()); 157 } 158 159 platform_observer_.reset( 160 OpaqueBrowserFrameViewPlatformSpecific::Create(this, layout_)); 161} 162 163OpaqueBrowserFrameView::~OpaqueBrowserFrameView() { 164} 165 166/////////////////////////////////////////////////////////////////////////////// 167// OpaqueBrowserFrameView, BrowserNonClientFrameView implementation: 168 169gfx::Rect OpaqueBrowserFrameView::GetBoundsForTabStrip( 170 views::View* tabstrip) const { 171 if (!tabstrip) 172 return gfx::Rect(); 173 174 return layout_->GetBoundsForTabStrip(tabstrip->GetPreferredSize(), width()); 175} 176 177BrowserNonClientFrameView::TabStripInsets 178OpaqueBrowserFrameView::GetTabStripInsets(bool restored) const { 179 if (!browser_view()->IsTabStripVisible()) 180 return TabStripInsets(); 181 // TODO: include OTR and caption. 182 return TabStripInsets(layout_->GetTabStripInsetsTop(restored), 0, 0); 183} 184 185int OpaqueBrowserFrameView::GetThemeBackgroundXInset() const { 186 return 0; 187} 188 189void OpaqueBrowserFrameView::UpdateThrobber(bool running) { 190 if (window_icon_) 191 window_icon_->Update(); 192} 193 194gfx::Size OpaqueBrowserFrameView::GetMinimumSize() { 195 return layout_->GetMinimumSize(width()); 196} 197 198/////////////////////////////////////////////////////////////////////////////// 199// OpaqueBrowserFrameView, views::NonClientFrameView implementation: 200 201gfx::Rect OpaqueBrowserFrameView::GetBoundsForClientView() const { 202 return layout_->client_view_bounds(); 203} 204 205gfx::Rect OpaqueBrowserFrameView::GetWindowBoundsForClientBounds( 206 const gfx::Rect& client_bounds) const { 207 return layout_->GetWindowBoundsForClientBounds(client_bounds); 208} 209 210int OpaqueBrowserFrameView::NonClientHitTest(const gfx::Point& point) { 211 if (!bounds().Contains(point)) 212 return HTNOWHERE; 213 214 // See if the point is within the avatar menu button or within the avatar 215 // label. 216 if ((avatar_button() && 217 avatar_button()->GetMirroredBounds().Contains(point)) || 218 (avatar_label() && avatar_label()->GetMirroredBounds().Contains(point))) 219 return HTCLIENT; 220 221 int frame_component = frame()->client_view()->NonClientHitTest(point); 222 223 // See if we're in the sysmenu region. We still have to check the tabstrip 224 // first so that clicks in a tab don't get treated as sysmenu clicks. 225 gfx::Rect sysmenu_rect(IconBounds()); 226 // In maximized mode we extend the rect to the screen corner to take advantage 227 // of Fitts' Law. 228 if (frame()->IsMaximized()) 229 sysmenu_rect.SetRect(0, 0, sysmenu_rect.right(), sysmenu_rect.bottom()); 230 sysmenu_rect.set_x(GetMirroredXForRect(sysmenu_rect)); 231 if (sysmenu_rect.Contains(point)) 232 return (frame_component == HTCLIENT) ? HTCLIENT : HTSYSMENU; 233 234 if (frame_component != HTNOWHERE) 235 return frame_component; 236 237 // Then see if the point is within any of the window controls. 238 if (close_button_ && close_button_->visible() && 239 close_button_->GetMirroredBounds().Contains(point)) 240 return HTCLOSE; 241 if (restore_button_ && restore_button_->visible() && 242 restore_button_->GetMirroredBounds().Contains(point)) 243 return HTMAXBUTTON; 244 if (maximize_button_ && maximize_button_->visible() && 245 maximize_button_->GetMirroredBounds().Contains(point)) 246 return HTMAXBUTTON; 247 if (minimize_button_ && minimize_button_->visible() && 248 minimize_button_->GetMirroredBounds().Contains(point)) 249 return HTMINBUTTON; 250 251 views::WidgetDelegate* delegate = frame()->widget_delegate(); 252 if (!delegate) { 253 LOG(WARNING) << "delegate is NULL, returning safe default."; 254 return HTCAPTION; 255 } 256 int window_component = GetHTComponentForFrame(point, TopResizeHeight(), 257 NonClientBorderThickness(), kResizeAreaCornerSize, kResizeAreaCornerSize, 258 delegate->CanResize()); 259 // Fall back to the caption if no other component matches. 260 return (window_component == HTNOWHERE) ? HTCAPTION : window_component; 261} 262 263void OpaqueBrowserFrameView::GetWindowMask(const gfx::Size& size, 264 gfx::Path* window_mask) { 265 DCHECK(window_mask); 266 267 if (frame()->IsMaximized() || frame()->IsFullscreen()) 268 return; 269 270 views::GetDefaultWindowMask(size, window_mask); 271} 272 273void OpaqueBrowserFrameView::ResetWindowControls() { 274 if (!OpaqueBrowserFrameViewLayout::ShouldAddDefaultCaptionButtons()) 275 return; 276 restore_button_->SetState(views::CustomButton::STATE_NORMAL); 277 minimize_button_->SetState(views::CustomButton::STATE_NORMAL); 278 maximize_button_->SetState(views::CustomButton::STATE_NORMAL); 279 // The close button isn't affected by this constraint. 280} 281 282void OpaqueBrowserFrameView::UpdateWindowIcon() { 283 window_icon_->SchedulePaint(); 284} 285 286void OpaqueBrowserFrameView::UpdateWindowTitle() { 287 if (!frame()->IsFullscreen()) 288 window_title_->SchedulePaint(); 289} 290 291/////////////////////////////////////////////////////////////////////////////// 292// OpaqueBrowserFrameView, views::View overrides: 293 294void OpaqueBrowserFrameView::OnPaint(gfx::Canvas* canvas) { 295 if (frame()->IsFullscreen()) 296 return; // Nothing is visible, so don't bother to paint. 297 298 if (frame()->IsMaximized()) 299 PaintMaximizedFrameBorder(canvas); 300 else 301 PaintRestoredFrameBorder(canvas); 302 303 // The window icon and title are painted by their respective views. 304 /* TODO(pkasting): If this window is active, we should also draw a drop 305 * shadow on the title. This is tricky, because we don't want to hardcode a 306 * shadow color (since we want to work with various themes), but we can't 307 * alpha-blend either (since the Windows text APIs don't really do this). 308 * So we'd need to sample the background color at the right location and 309 * synthesize a good shadow color. */ 310 311 if (browser_view()->IsToolbarVisible()) 312 PaintToolbarBackground(canvas); 313 if (!frame()->IsMaximized()) 314 PaintRestoredClientEdge(canvas); 315} 316 317bool OpaqueBrowserFrameView::HitTestRect(const gfx::Rect& rect) const { 318 if (!views::View::HitTestRect(rect)) { 319 // |rect| is outside OpaqueBrowserFrameView's bounds. 320 return false; 321 } 322 323 // If the rect is outside the bounds of the client area, claim it. 324 // TODO(tdanderson): Implement View::ConvertRectToTarget(). 325 gfx::Point rect_in_client_view_coords_origin(rect.origin()); 326 View::ConvertPointToTarget(this, frame()->client_view(), 327 &rect_in_client_view_coords_origin); 328 gfx::Rect rect_in_client_view_coords( 329 rect_in_client_view_coords_origin, rect.size()); 330 if (!frame()->client_view()->HitTestRect(rect_in_client_view_coords)) 331 return true; 332 333 // Otherwise, claim |rect| only if it is above the bottom of the tabstrip in 334 // a non-tab portion. 335 TabStrip* tabstrip = browser_view()->tabstrip(); 336 if (!tabstrip || !browser_view()->IsTabStripVisible()) 337 return false; 338 339 gfx::Point rect_in_tabstrip_coords_origin(rect.origin()); 340 View::ConvertPointToTarget(this, tabstrip, 341 &rect_in_tabstrip_coords_origin); 342 gfx::Rect rect_in_tabstrip_coords( 343 rect_in_tabstrip_coords_origin, rect.size()); 344 345 if (rect_in_tabstrip_coords.bottom() > tabstrip->GetLocalBounds().bottom()) { 346 // |rect| is below the tabstrip. 347 return false; 348 } 349 350 if (tabstrip->HitTestRect(rect_in_tabstrip_coords)) { 351 // Claim |rect| if it is in a non-tab portion of the tabstrip. 352 // TODO(tdanderson): Pass |rect_in_tabstrip_coords| instead of its center 353 // point to TabStrip::IsPositionInWindowCaption() once 354 // GetEventHandlerForRect() is implemented. 355 return tabstrip->IsPositionInWindowCaption( 356 rect_in_tabstrip_coords.CenterPoint()); 357 } 358 359 // The window switcher button is to the right of the tabstrip but is 360 // part of the client view. 361 views::View* window_switcher_button = 362 browser_view()->window_switcher_button(); 363 if (window_switcher_button && window_switcher_button->visible()) { 364 gfx::Point rect_in_window_switcher_coords_origin(rect.origin()); 365 View::ConvertPointToTarget(this, window_switcher_button, 366 &rect_in_window_switcher_coords_origin); 367 gfx::Rect rect_in_window_switcher_coords( 368 rect_in_window_switcher_coords_origin, rect.size()); 369 370 if (window_switcher_button->HitTestRect(rect_in_window_switcher_coords)) 371 return false; 372 } 373 374 // We claim |rect| because it is above the bottom of the tabstrip, but 375 // neither in the tabstrip nor in the window switcher button. In particular, 376 // the avatar label/button is left of the tabstrip and the window controls 377 // are right of the tabstrip. 378 return true; 379} 380 381void OpaqueBrowserFrameView::GetAccessibleState( 382 ui::AccessibleViewState* state) { 383 state->role = ui::AccessibilityTypes::ROLE_TITLEBAR; 384} 385 386/////////////////////////////////////////////////////////////////////////////// 387// OpaqueBrowserFrameView, views::ButtonListener implementation: 388 389void OpaqueBrowserFrameView::ButtonPressed(views::Button* sender, 390 const ui::Event& event) { 391 if (sender == minimize_button_) 392 frame()->Minimize(); 393 else if (sender == maximize_button_) 394 frame()->Maximize(); 395 else if (sender == restore_button_) 396 frame()->Restore(); 397 else if (sender == close_button_) 398 frame()->Close(); 399} 400 401/////////////////////////////////////////////////////////////////////////////// 402// OpaqueBrowserFrameView, TabIconView::TabContentsProvider implementation: 403 404bool OpaqueBrowserFrameView::ShouldTabIconViewAnimate() const { 405 // This function is queried during the creation of the window as the 406 // TabIconView we host is initialized, so we need to NULL check the selected 407 // WebContents because in this condition there is not yet a selected tab. 408 WebContents* current_tab = browser_view()->GetActiveWebContents(); 409 return current_tab ? current_tab->IsLoading() : false; 410} 411 412gfx::ImageSkia OpaqueBrowserFrameView::GetFaviconForTabIconView() { 413 views::WidgetDelegate* delegate = frame()->widget_delegate(); 414 if (!delegate) { 415 LOG(WARNING) << "delegate is NULL, returning safe default."; 416 return gfx::ImageSkia(); 417 } 418 return delegate->GetWindowIcon(); 419} 420 421/////////////////////////////////////////////////////////////////////////////// 422// OpaqueBrowserFrameView, protected: 423 424void OpaqueBrowserFrameView::Observe( 425 int type, 426 const content::NotificationSource& source, 427 const content::NotificationDetails& details) { 428 switch (type) { 429 case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED: 430 UpdateAvatarInfo(); 431 break; 432 default: 433 NOTREACHED() << "Got a notification we didn't register for!"; 434 break; 435 } 436} 437 438/////////////////////////////////////////////////////////////////////////////// 439// OpaqueBrowserFrameView, OpaqueBrowserFrameViewLayoutDelegate implementation: 440 441bool OpaqueBrowserFrameView::ShouldShowWindowIcon() const { 442 views::WidgetDelegate* delegate = frame()->widget_delegate(); 443 return delegate && delegate->ShouldShowWindowIcon(); 444} 445 446bool OpaqueBrowserFrameView::ShouldShowWindowTitle() const { 447 // |delegate| may be NULL if called from callback of InputMethodChanged while 448 // a window is being destroyed. 449 // See more discussion at http://crosbug.com/8958 450 views::WidgetDelegate* delegate = frame()->widget_delegate(); 451 return delegate && delegate->ShouldShowWindowTitle(); 452} 453 454string16 OpaqueBrowserFrameView::GetWindowTitle() const { 455 return frame()->widget_delegate()->GetWindowTitle(); 456} 457 458int OpaqueBrowserFrameView::GetIconSize() const { 459#if defined(OS_WIN) 460 // This metric scales up if either the titlebar height or the titlebar font 461 // size are increased. 462 return GetSystemMetrics(SM_CYSMICON); 463#else 464 return std::max(BrowserFrame::GetTitleFont().GetHeight(), kIconMinimumSize); 465#endif 466} 467 468bool OpaqueBrowserFrameView::ShouldLeaveOffsetNearTopBorder() const { 469 return frame()->ShouldLeaveOffsetNearTopBorder(); 470} 471 472gfx::Size OpaqueBrowserFrameView::GetBrowserViewMinimumSize() const { 473 return browser_view()->GetMinimumSize(); 474} 475 476bool OpaqueBrowserFrameView::ShouldShowAvatar() const { 477 return browser_view()->ShouldShowAvatar(); 478} 479 480gfx::ImageSkia OpaqueBrowserFrameView::GetOTRAvatarIcon() const { 481 return browser_view()->GetOTRAvatarIcon(); 482} 483 484bool OpaqueBrowserFrameView::IsMaximized() const { 485 return frame()->IsMaximized(); 486} 487 488bool OpaqueBrowserFrameView::IsMinimized() const { 489 return frame()->IsMinimized(); 490} 491 492bool OpaqueBrowserFrameView::IsFullscreen() const { 493 return frame()->IsFullscreen(); 494} 495 496bool OpaqueBrowserFrameView::IsTabStripVisible() const { 497 return browser_view()->IsTabStripVisible(); 498} 499 500int OpaqueBrowserFrameView::GetTabStripHeight() const { 501 return browser_view()->GetTabStripHeight(); 502} 503 504int OpaqueBrowserFrameView::GetAdditionalReservedSpaceInTabStrip() const { 505 // We don't have the sysmenu buttons in Windows 8 metro mode. However there 506 // are buttons like the window switcher which are drawn in the non client 507 // are in the BrowserView. We need to ensure that the tab strip does not 508 // draw on the window switcher button. 509 views::View* button = browser_view()->window_switcher_button(); 510 return button ? button->width() : 0; 511} 512 513gfx::Size OpaqueBrowserFrameView::GetTabstripPreferredSize() const { 514 gfx::Size s = browser_view()->tabstrip()->GetPreferredSize(); 515 return s; 516} 517 518/////////////////////////////////////////////////////////////////////////////// 519// OpaqueBrowserFrameView, private: 520 521views::ImageButton* OpaqueBrowserFrameView::InitWindowCaptionButton( 522 int normal_image_id, 523 int hot_image_id, 524 int pushed_image_id, 525 int mask_image_id, 526 int accessibility_string_id, 527 ViewID view_id) { 528 views::ImageButton* button = new views::ImageButton(this); 529 ui::ThemeProvider* tp = frame()->GetThemeProvider(); 530 button->SetImage(views::CustomButton::STATE_NORMAL, 531 tp->GetImageSkiaNamed(normal_image_id)); 532 button->SetImage(views::CustomButton::STATE_HOVERED, 533 tp->GetImageSkiaNamed(hot_image_id)); 534 button->SetImage(views::CustomButton::STATE_PRESSED, 535 tp->GetImageSkiaNamed(pushed_image_id)); 536 if (browser_view()->IsBrowserTypeNormal()) { 537 button->SetBackground( 538 tp->GetColor(ThemeProperties::COLOR_BUTTON_BACKGROUND), 539 tp->GetImageSkiaNamed(IDR_THEME_WINDOW_CONTROL_BACKGROUND), 540 tp->GetImageSkiaNamed(mask_image_id)); 541 } 542 button->SetAccessibleName( 543 l10n_util::GetStringUTF16(accessibility_string_id)); 544 button->set_id(view_id); 545 AddChildView(button); 546 return button; 547} 548 549int OpaqueBrowserFrameView::FrameBorderThickness(bool restored) const { 550 return layout_->FrameBorderThickness(restored); 551} 552 553int OpaqueBrowserFrameView::TopResizeHeight() const { 554 return FrameBorderThickness(false) - kTopResizeAdjust; 555} 556 557int OpaqueBrowserFrameView::NonClientBorderThickness() const { 558 return layout_->NonClientBorderThickness(); 559} 560 561gfx::Rect OpaqueBrowserFrameView::IconBounds() const { 562 return layout_->IconBounds(); 563} 564 565void OpaqueBrowserFrameView::PaintRestoredFrameBorder(gfx::Canvas* canvas) { 566 frame_background_->set_frame_color(GetFrameColor()); 567 frame_background_->set_theme_image(GetFrameImage()); 568 frame_background_->set_theme_overlay_image(GetFrameOverlayImage()); 569 frame_background_->set_top_area_height(GetTopAreaHeight()); 570 571 ui::ThemeProvider* tp = GetThemeProvider(); 572 frame_background_->SetSideImages( 573 tp->GetImageSkiaNamed(IDR_WINDOW_LEFT_SIDE), 574 tp->GetImageSkiaNamed(IDR_WINDOW_TOP_CENTER), 575 tp->GetImageSkiaNamed(IDR_WINDOW_RIGHT_SIDE), 576 tp->GetImageSkiaNamed(IDR_WINDOW_BOTTOM_CENTER)); 577 frame_background_->SetCornerImages( 578 tp->GetImageSkiaNamed(IDR_WINDOW_TOP_LEFT_CORNER), 579 tp->GetImageSkiaNamed(IDR_WINDOW_TOP_RIGHT_CORNER), 580 tp->GetImageSkiaNamed(IDR_WINDOW_BOTTOM_LEFT_CORNER), 581 tp->GetImageSkiaNamed(IDR_WINDOW_BOTTOM_RIGHT_CORNER)); 582 frame_background_->PaintRestored(canvas, this); 583 584 // Note: When we don't have a toolbar, we need to draw some kind of bottom 585 // edge here. Because the App Window graphics we use for this have an 586 // attached client edge and their sizing algorithm is a little involved, we do 587 // all this in PaintRestoredClientEdge(). 588} 589 590void OpaqueBrowserFrameView::PaintMaximizedFrameBorder(gfx::Canvas* canvas) { 591 frame_background_->set_frame_color(GetFrameColor()); 592 frame_background_->set_theme_image(GetFrameImage()); 593 frame_background_->set_theme_overlay_image(GetFrameOverlayImage()); 594 frame_background_->set_top_area_height(GetTopAreaHeight()); 595 596 // Theme frame must be aligned with the tabstrip as if we were 597 // in restored mode. Note that the top of the tabstrip is 598 // kTabstripTopShadowThickness px off the top of the screen. 599 int theme_background_y = -(GetTabStripInsets(true).top + 600 kTabstripTopShadowThickness); 601 frame_background_->set_theme_background_y(theme_background_y); 602 603 frame_background_->PaintMaximized(canvas, this); 604 605 // TODO(jamescook): Migrate this into FrameBackground. 606 if (!browser_view()->IsToolbarVisible()) { 607 // There's no toolbar to edge the frame border, so we need to draw a bottom 608 // edge. The graphic we use for this has a built in client edge, so we clip 609 // it off the bottom. 610 gfx::ImageSkia* top_center = 611 GetThemeProvider()->GetImageSkiaNamed(IDR_APP_TOP_CENTER); 612 int edge_height = top_center->height() - kClientEdgeThickness; 613 canvas->TileImageInt(*top_center, 0, 614 frame()->client_view()->y() - edge_height, width(), edge_height); 615 } 616} 617 618void OpaqueBrowserFrameView::PaintToolbarBackground(gfx::Canvas* canvas) { 619 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds()); 620 if (toolbar_bounds.IsEmpty()) 621 return; 622 gfx::Point toolbar_origin(toolbar_bounds.origin()); 623 ConvertPointToTarget(browser_view(), this, &toolbar_origin); 624 toolbar_bounds.set_origin(toolbar_origin); 625 626 int x = toolbar_bounds.x(); 627 int w = toolbar_bounds.width(); 628 int y = toolbar_bounds.y(); 629 int h = toolbar_bounds.height(); 630 631 // Gross hack: We split the toolbar images into two pieces, since sometimes 632 // (popup mode) the toolbar isn't tall enough to show the whole image. The 633 // split happens between the top shadow section and the bottom gradient 634 // section so that we never break the gradient. 635 int split_point = kFrameShadowThickness * 2; 636 int bottom_y = y + split_point; 637 ui::ThemeProvider* tp = GetThemeProvider(); 638 gfx::ImageSkia* toolbar_left = tp->GetImageSkiaNamed( 639 IDR_CONTENT_TOP_LEFT_CORNER); 640 int bottom_edge_height = std::min(toolbar_left->height(), h) - split_point; 641 642 // Split our canvas out so we can mask out the corners of the toolbar 643 // without masking out the frame. 644 canvas->SaveLayerAlpha( 645 255, gfx::Rect(x - kClientEdgeThickness, y, w + kClientEdgeThickness * 3, 646 h)); 647 648 // Paint the bottom rect. 649 canvas->FillRect(gfx::Rect(x, bottom_y, w, bottom_edge_height), 650 tp->GetColor(ThemeProperties::COLOR_TOOLBAR)); 651 652 // Tile the toolbar image starting at the frame edge on the left and where the 653 // horizontal tabstrip is (or would be) on the top. 654 gfx::ImageSkia* theme_toolbar = tp->GetImageSkiaNamed(IDR_THEME_TOOLBAR); 655 canvas->TileImageInt(*theme_toolbar, 656 x + GetThemeBackgroundXInset(), 657 bottom_y - GetTabStripInsets(false).top, 658 x, bottom_y, w, theme_toolbar->height()); 659 660 // Draw rounded corners for the tab. 661 gfx::ImageSkia* toolbar_left_mask = 662 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_LEFT_CORNER_MASK); 663 gfx::ImageSkia* toolbar_right_mask = 664 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_RIGHT_CORNER_MASK); 665 666 // We mask out the corners by using the DestinationIn transfer mode, 667 // which keeps the RGB pixels from the destination and the alpha from 668 // the source. 669 SkPaint paint; 670 paint.setXfermodeMode(SkXfermode::kDstIn_Mode); 671 672 // Mask the left edge. 673 int left_x = x - kContentEdgeShadowThickness; 674 canvas->DrawImageInt(*toolbar_left_mask, 0, 0, toolbar_left_mask->width(), 675 split_point, left_x, y, toolbar_left_mask->width(), 676 split_point, false, paint); 677 canvas->DrawImageInt(*toolbar_left_mask, 0, 678 toolbar_left_mask->height() - bottom_edge_height, 679 toolbar_left_mask->width(), bottom_edge_height, left_x, bottom_y, 680 toolbar_left_mask->width(), bottom_edge_height, false, paint); 681 682 // Mask the right edge. 683 int right_x = 684 x + w - toolbar_right_mask->width() + kContentEdgeShadowThickness; 685 canvas->DrawImageInt(*toolbar_right_mask, 0, 0, toolbar_right_mask->width(), 686 split_point, right_x, y, toolbar_right_mask->width(), 687 split_point, false, paint); 688 canvas->DrawImageInt(*toolbar_right_mask, 0, 689 toolbar_right_mask->height() - bottom_edge_height, 690 toolbar_right_mask->width(), bottom_edge_height, right_x, bottom_y, 691 toolbar_right_mask->width(), bottom_edge_height, false, paint); 692 canvas->Restore(); 693 694 canvas->DrawImageInt(*toolbar_left, 0, 0, toolbar_left->width(), split_point, 695 left_x, y, toolbar_left->width(), split_point, false); 696 canvas->DrawImageInt(*toolbar_left, 0, 697 toolbar_left->height() - bottom_edge_height, toolbar_left->width(), 698 bottom_edge_height, left_x, bottom_y, toolbar_left->width(), 699 bottom_edge_height, false); 700 701 gfx::ImageSkia* toolbar_center = 702 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_CENTER); 703 canvas->TileImageInt(*toolbar_center, 0, 0, left_x + toolbar_left->width(), 704 y, right_x - (left_x + toolbar_left->width()), 705 split_point); 706 707 gfx::ImageSkia* toolbar_right = tp->GetImageSkiaNamed( 708 IDR_CONTENT_TOP_RIGHT_CORNER); 709 canvas->DrawImageInt(*toolbar_right, 0, 0, toolbar_right->width(), 710 split_point, right_x, y, toolbar_right->width(), split_point, false); 711 canvas->DrawImageInt(*toolbar_right, 0, 712 toolbar_right->height() - bottom_edge_height, toolbar_right->width(), 713 bottom_edge_height, right_x, bottom_y, toolbar_right->width(), 714 bottom_edge_height, false); 715 716 // Draw the content/toolbar separator. 717 canvas->FillRect( 718 gfx::Rect(x + kClientEdgeThickness, 719 toolbar_bounds.bottom() - kClientEdgeThickness, 720 w - (2 * kClientEdgeThickness), 721 kClientEdgeThickness), 722 ThemeProperties::GetDefaultColor( 723 ThemeProperties::COLOR_TOOLBAR_SEPARATOR)); 724} 725 726void OpaqueBrowserFrameView::PaintRestoredClientEdge(gfx::Canvas* canvas) { 727 ui::ThemeProvider* tp = GetThemeProvider(); 728 int client_area_top = frame()->client_view()->y(); 729 int image_top = client_area_top; 730 731 gfx::Rect client_area_bounds = 732 layout_->CalculateClientAreaBounds(width(), height()); 733 SkColor toolbar_color = tp->GetColor(ThemeProperties::COLOR_TOOLBAR); 734 735 if (browser_view()->IsToolbarVisible()) { 736 // The client edge images always start below the toolbar corner images. The 737 // client edge filled rects start there or at the bottom of the toolbar, 738 // whichever is shorter. 739 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds()); 740 image_top += toolbar_bounds.y() + 741 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_LEFT_CORNER)->height(); 742 client_area_top = std::min(image_top, 743 client_area_top + toolbar_bounds.bottom() - kClientEdgeThickness); 744 } else if (!browser_view()->IsTabStripVisible()) { 745 // The toolbar isn't going to draw a client edge for us, so draw one 746 // ourselves. 747 gfx::ImageSkia* top_left = tp->GetImageSkiaNamed(IDR_APP_TOP_LEFT); 748 gfx::ImageSkia* top_center = tp->GetImageSkiaNamed(IDR_APP_TOP_CENTER); 749 gfx::ImageSkia* top_right = tp->GetImageSkiaNamed(IDR_APP_TOP_RIGHT); 750 int top_edge_y = client_area_top - top_center->height(); 751 int height = client_area_top - top_edge_y; 752 753 canvas->DrawImageInt(*top_left, 0, 0, top_left->width(), height, 754 client_area_bounds.x() - top_left->width(), top_edge_y, 755 top_left->width(), height, false); 756 canvas->TileImageInt(*top_center, 0, 0, client_area_bounds.x(), top_edge_y, 757 client_area_bounds.width(), std::min(height, top_center->height())); 758 canvas->DrawImageInt(*top_right, 0, 0, top_right->width(), height, 759 client_area_bounds.right(), top_edge_y, 760 top_right->width(), height, false); 761 762 // Draw the toolbar color across the top edge. 763 canvas->FillRect(gfx::Rect(client_area_bounds.x() - kClientEdgeThickness, 764 client_area_top - kClientEdgeThickness, 765 client_area_bounds.width() + (2 * kClientEdgeThickness), 766 kClientEdgeThickness), toolbar_color); 767 } 768 769 int client_area_bottom = 770 std::max(client_area_top, height() - NonClientBorderThickness()); 771 int image_height = client_area_bottom - image_top; 772 773 // Draw the client edge images. 774 gfx::ImageSkia* right = tp->GetImageSkiaNamed(IDR_CONTENT_RIGHT_SIDE); 775 canvas->TileImageInt(*right, client_area_bounds.right(), image_top, 776 right->width(), image_height); 777 canvas->DrawImageInt( 778 *tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_RIGHT_CORNER), 779 client_area_bounds.right(), client_area_bottom); 780 gfx::ImageSkia* bottom = tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_CENTER); 781 canvas->TileImageInt(*bottom, client_area_bounds.x(), 782 client_area_bottom, client_area_bounds.width(), 783 bottom->height()); 784 gfx::ImageSkia* bottom_left = 785 tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_LEFT_CORNER); 786 canvas->DrawImageInt(*bottom_left, 787 client_area_bounds.x() - bottom_left->width(), client_area_bottom); 788 gfx::ImageSkia* left = tp->GetImageSkiaNamed(IDR_CONTENT_LEFT_SIDE); 789 canvas->TileImageInt(*left, client_area_bounds.x() - left->width(), 790 image_top, left->width(), image_height); 791 792 // Draw the toolbar color so that the client edges show the right color even 793 // where not covered by the toolbar image. NOTE: We do this after drawing the 794 // images because the images are meant to alpha-blend atop the frame whereas 795 // these rects are meant to be fully opaque, without anything overlaid. 796 canvas->FillRect(gfx::Rect(client_area_bounds.x() - kClientEdgeThickness, 797 client_area_top, kClientEdgeThickness, 798 client_area_bottom + kClientEdgeThickness - client_area_top), 799 toolbar_color); 800 canvas->FillRect(gfx::Rect(client_area_bounds.x(), client_area_bottom, 801 client_area_bounds.width(), kClientEdgeThickness), 802 toolbar_color); 803 canvas->FillRect(gfx::Rect(client_area_bounds.right(), client_area_top, 804 kClientEdgeThickness, 805 client_area_bottom + kClientEdgeThickness - client_area_top), 806 toolbar_color); 807} 808 809SkColor OpaqueBrowserFrameView::GetFrameColor() const { 810 bool is_incognito = browser_view()->IsOffTheRecord(); 811 if (browser_view()->IsBrowserTypeNormal()) { 812 if (ShouldPaintAsActive()) { 813 return GetThemeProvider()->GetColor(is_incognito ? 814 ThemeProperties::COLOR_FRAME_INCOGNITO : 815 ThemeProperties::COLOR_FRAME); 816 } 817 return GetThemeProvider()->GetColor(is_incognito ? 818 ThemeProperties::COLOR_FRAME_INCOGNITO_INACTIVE : 819 ThemeProperties::COLOR_FRAME_INACTIVE); 820 } 821 // Never theme app and popup windows. 822 if (ShouldPaintAsActive()) { 823 return ThemeProperties::GetDefaultColor(is_incognito ? 824 ThemeProperties::COLOR_FRAME_INCOGNITO : ThemeProperties::COLOR_FRAME); 825 } 826 return ThemeProperties::GetDefaultColor(is_incognito ? 827 ThemeProperties::COLOR_FRAME_INCOGNITO_INACTIVE : 828 ThemeProperties::COLOR_FRAME_INACTIVE); 829} 830 831gfx::ImageSkia* OpaqueBrowserFrameView::GetFrameImage() const { 832 bool is_incognito = browser_view()->IsOffTheRecord(); 833 int resource_id; 834 if (browser_view()->IsBrowserTypeNormal()) { 835 if (ShouldPaintAsActive()) { 836 resource_id = is_incognito ? 837 IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME; 838 } else { 839 resource_id = is_incognito ? 840 IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE; 841 } 842 return GetThemeProvider()->GetImageSkiaNamed(resource_id); 843 } 844 // Never theme app and popup windows. 845 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 846 if (ShouldPaintAsActive()) { 847 resource_id = is_incognito ? 848 IDR_THEME_FRAME_INCOGNITO : IDR_FRAME; 849 } else { 850 resource_id = is_incognito ? 851 IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE; 852 } 853 return rb.GetImageSkiaNamed(resource_id); 854} 855 856gfx::ImageSkia* OpaqueBrowserFrameView::GetFrameOverlayImage() const { 857 ui::ThemeProvider* tp = GetThemeProvider(); 858 if (tp->HasCustomImage(IDR_THEME_FRAME_OVERLAY) && 859 browser_view()->IsBrowserTypeNormal() && 860 !browser_view()->IsOffTheRecord()) { 861 return tp->GetImageSkiaNamed(ShouldPaintAsActive() ? 862 IDR_THEME_FRAME_OVERLAY : IDR_THEME_FRAME_OVERLAY_INACTIVE); 863 } 864 return NULL; 865} 866 867int OpaqueBrowserFrameView::GetTopAreaHeight() const { 868 gfx::ImageSkia* frame_image = GetFrameImage(); 869 int top_area_height = frame_image->height(); 870 if (browser_view()->IsTabStripVisible()) { 871 top_area_height = std::max(top_area_height, 872 GetBoundsForTabStrip(browser_view()->tabstrip()).bottom()); 873 } 874 return top_area_height; 875} 876