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