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