browser_header_painter_ash.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
1// Copyright 2014 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_header_painter_ash.h"
6
7#include "ash/frame/caption_buttons/frame_caption_button_container_view.h"
8#include "ash/frame/header_painter_util.h"
9#include "base/logging.h"  // DCHECK
10#include "chrome/browser/ui/browser.h"
11#include "chrome/browser/ui/views/frame/browser_frame.h"
12#include "chrome/browser/ui/views/frame/browser_view.h"
13#include "grit/ash_resources.h"
14#include "grit/theme_resources.h"
15#include "third_party/skia/include/core/SkCanvas.h"
16#include "third_party/skia/include/core/SkColor.h"
17#include "third_party/skia/include/core/SkPaint.h"
18#include "third_party/skia/include/core/SkPath.h"
19#include "ui/base/resource/resource_bundle.h"
20#include "ui/base/theme_provider.h"
21#include "ui/gfx/animation/slide_animation.h"
22#include "ui/gfx/canvas.h"
23#include "ui/gfx/image/image_skia.h"
24#include "ui/gfx/rect.h"
25#include "ui/gfx/skia_util.h"
26#include "ui/views/view.h"
27#include "ui/views/widget/widget.h"
28#include "ui/views/widget/widget_delegate.h"
29
30using views::Widget;
31
32namespace {
33// Color for the restored window title text.
34const SkColor kRestoredWindowTitleTextColor = SkColorSetRGB(40, 40, 40);
35// Color for the maximized window title text.
36const SkColor kMaximizedWindowTitleTextColor = SK_ColorWHITE;
37// Duration of crossfade animation for activating and deactivating frame.
38const int kActivationCrossfadeDurationMs = 200;
39
40// Tiles an image into an area, rounding the top corners. Samples |image|
41// starting |image_inset_x| pixels from the left of the image.
42void TileRoundRect(gfx::Canvas* canvas,
43                   const gfx::ImageSkia& image,
44                   const SkPaint& paint,
45                   const gfx::Rect& bounds,
46                   int top_left_corner_radius,
47                   int top_right_corner_radius,
48                   int image_inset_x) {
49  SkRect rect = gfx::RectToSkRect(bounds);
50  const SkScalar kTopLeftRadius = SkIntToScalar(top_left_corner_radius);
51  const SkScalar kTopRightRadius = SkIntToScalar(top_right_corner_radius);
52  SkScalar radii[8] = {
53      kTopLeftRadius, kTopLeftRadius,  // top-left
54      kTopRightRadius, kTopRightRadius,  // top-right
55      0, 0,   // bottom-right
56      0, 0};  // bottom-left
57  SkPath path;
58  path.addRoundRect(rect, radii, SkPath::kCW_Direction);
59  canvas->DrawImageInPath(image, -image_inset_x, 0, path, paint);
60}
61
62// Tiles |frame_image| and |frame_overlay_image| into an area, rounding the top
63// corners.
64void PaintFrameImagesInRoundRect(gfx::Canvas* canvas,
65                                 const gfx::ImageSkia& frame_image,
66                                 const gfx::ImageSkia& frame_overlay_image,
67                                 const SkPaint& paint,
68                                 const gfx::Rect& bounds,
69                                 int corner_radius,
70                                 int image_inset_x) {
71  SkXfermode::Mode normal_mode;
72  SkXfermode::AsMode(NULL, &normal_mode);
73
74  // If |paint| is using an unusual SkXfermode::Mode (this is the case while
75  // crossfading), we must create a new canvas to overlay |frame_image| and
76  // |frame_overlay_image| using |normal_mode| and then paint the result
77  // using the unusual mode. We try to avoid this because creating a new
78  // browser-width canvas is expensive.
79  bool fast_path = (frame_overlay_image.isNull() ||
80      SkXfermode::IsMode(paint.getXfermode(), normal_mode));
81  if (fast_path) {
82    TileRoundRect(canvas, frame_image, paint, bounds, corner_radius,
83        corner_radius, image_inset_x);
84
85    if (!frame_overlay_image.isNull()) {
86      // Adjust |bounds| such that |frame_overlay_image| is not tiled.
87      gfx::Rect overlay_bounds = bounds;
88      overlay_bounds.Intersect(
89          gfx::Rect(bounds.origin(), frame_overlay_image.size()));
90      int top_left_corner_radius = corner_radius;
91      int top_right_corner_radius = corner_radius;
92      if (overlay_bounds.width() < bounds.width() - corner_radius)
93        top_right_corner_radius = 0;
94      TileRoundRect(canvas, frame_overlay_image, paint, overlay_bounds,
95          top_left_corner_radius, top_right_corner_radius, 0);
96    }
97  } else {
98    gfx::Canvas temporary_canvas(bounds.size(), canvas->image_scale(), false);
99    temporary_canvas.TileImageInt(frame_image,
100                                  image_inset_x, 0,
101                                  0, 0,
102                                  bounds.width(), bounds.height());
103    temporary_canvas.DrawImageInt(frame_overlay_image, 0, 0);
104    TileRoundRect(canvas, gfx::ImageSkia(temporary_canvas.ExtractImageRep()),
105        paint, bounds, corner_radius, corner_radius, 0);
106  }
107}
108
109}  // namespace
110
111///////////////////////////////////////////////////////////////////////////////
112// BrowserHeaderPainterAsh, public:
113
114BrowserHeaderPainterAsh::BrowserHeaderPainterAsh()
115    : frame_(NULL),
116      is_tabbed_(false),
117      is_incognito_(false),
118      view_(NULL),
119      window_icon_(NULL),
120      caption_button_container_(NULL),
121      painted_height_(0),
122      initial_paint_(true),
123      mode_(MODE_INACTIVE),
124      activation_animation_(new gfx::SlideAnimation(this)) {
125}
126
127BrowserHeaderPainterAsh::~BrowserHeaderPainterAsh() {
128}
129
130void BrowserHeaderPainterAsh::Init(
131    views::Widget* frame,
132    BrowserView* browser_view,
133    views::View* header_view,
134    views::View* window_icon,
135    ash::FrameCaptionButtonContainerView* caption_button_container) {
136  DCHECK(frame);
137  DCHECK(browser_view);
138  DCHECK(header_view);
139  // window_icon may be NULL.
140  DCHECK(caption_button_container);
141  frame_ = frame;
142
143  is_tabbed_ = browser_view->browser()->is_type_tabbed();
144  is_incognito_ = !browser_view->IsRegularOrGuestSession();
145
146  view_ = header_view;
147  window_icon_ = window_icon;
148  caption_button_container_ = caption_button_container;
149}
150
151int BrowserHeaderPainterAsh::GetMinimumHeaderWidth() const {
152  // Ensure we have enough space for the window icon and buttons. We allow
153  // the title string to collapse to zero width.
154  return GetTitleBounds().x() +
155      caption_button_container_->GetMinimumSize().width();
156}
157
158void BrowserHeaderPainterAsh::PaintHeader(gfx::Canvas* canvas, Mode mode) {
159  Mode old_mode = mode_;
160  mode_ = mode;
161
162  if (mode_ != old_mode) {
163    if (!initial_paint_ &&
164        ash::HeaderPainterUtil::CanAnimateActivation(frame_)) {
165      activation_animation_->SetSlideDuration(kActivationCrossfadeDurationMs);
166      if (mode_ == MODE_ACTIVE)
167        activation_animation_->Show();
168      else
169        activation_animation_->Hide();
170    } else {
171      if (mode_ == MODE_ACTIVE)
172        activation_animation_->Reset(1);
173      else
174        activation_animation_->Reset(0);
175    }
176    initial_paint_ = false;
177  }
178
179  int corner_radius = (frame_->IsMaximized() || frame_->IsFullscreen()) ?
180      0 : ash::HeaderPainterUtil::GetTopCornerRadiusWhenRestored();
181
182  int active_alpha = activation_animation_->CurrentValueBetween(0, 255);
183  int inactive_alpha = 255 - active_alpha;
184
185  SkPaint paint;
186  if (inactive_alpha > 0) {
187    if (active_alpha > 0)
188      paint.setXfermodeMode(SkXfermode::kPlus_Mode);
189
190    gfx::ImageSkia inactive_frame_image;
191    gfx::ImageSkia inactive_frame_overlay_image;
192    GetFrameImages(MODE_INACTIVE, &inactive_frame_image,
193        &inactive_frame_overlay_image);
194
195    paint.setAlpha(inactive_alpha);
196    PaintFrameImagesInRoundRect(
197        canvas,
198        inactive_frame_image,
199        inactive_frame_overlay_image,
200        paint,
201        GetPaintedBounds(),
202        corner_radius,
203        ash::HeaderPainterUtil::GetThemeBackgroundXInset());
204  }
205
206  if (active_alpha > 0) {
207    gfx::ImageSkia active_frame_image;
208    gfx::ImageSkia active_frame_overlay_image;
209    GetFrameImages(MODE_ACTIVE, &active_frame_image,
210        &active_frame_overlay_image);
211
212    paint.setAlpha(active_alpha);
213    PaintFrameImagesInRoundRect(
214        canvas,
215        active_frame_image,
216        active_frame_overlay_image,
217        paint,
218        GetPaintedBounds(),
219        corner_radius,
220        ash::HeaderPainterUtil::GetThemeBackgroundXInset());
221  }
222
223  if (!frame_->IsMaximized() && !frame_->IsFullscreen())
224    PaintHighlightForRestoredWindow(canvas);
225  if (frame_->widget_delegate() &&
226      frame_->widget_delegate()->ShouldShowWindowTitle()) {
227    PaintTitleBar(canvas);
228  }
229}
230
231void BrowserHeaderPainterAsh::LayoutHeader() {
232  // Purposefully set |painted_height_| to an invalid value. We cannot use
233  // |painted_height_| because the computation of |painted_height_| may depend
234  // on having laid out the window controls.
235  painted_height_ = -1;
236
237  UpdateCaptionButtonImages();
238  caption_button_container_->Layout();
239
240  gfx::Size caption_button_container_size =
241      caption_button_container_->GetPreferredSize();
242  caption_button_container_->SetBounds(
243      view_->width() - caption_button_container_size.width(),
244      0,
245      caption_button_container_size.width(),
246      caption_button_container_size.height());
247
248  if (window_icon_) {
249    // Vertically center the window icon with respect to the caption button
250    // container.
251    int icon_size = ash::HeaderPainterUtil::GetDefaultIconSize();
252    int icon_offset_y = (caption_button_container_->height() - icon_size) / 2;
253    window_icon_->SetBounds(ash::HeaderPainterUtil::GetIconXOffset(),
254        icon_offset_y, icon_size, icon_size);
255  }
256}
257
258int BrowserHeaderPainterAsh::GetHeaderHeightForPainting() const {
259  return painted_height_;
260}
261
262void BrowserHeaderPainterAsh::SetHeaderHeightForPainting(int height) {
263  painted_height_ = height;
264}
265
266void BrowserHeaderPainterAsh::SchedulePaintForTitle() {
267  view_->SchedulePaintInRect(GetTitleBounds());
268}
269
270///////////////////////////////////////////////////////////////////////////////
271// gfx::AnimationDelegate overrides:
272
273void BrowserHeaderPainterAsh::AnimationProgressed(
274    const gfx::Animation* animation) {
275  view_->SchedulePaintInRect(GetPaintedBounds());
276}
277
278///////////////////////////////////////////////////////////////////////////////
279// BrowserHeaderPainterAsh, private:
280
281void BrowserHeaderPainterAsh::PaintHighlightForRestoredWindow(
282    gfx::Canvas* canvas) {
283  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
284  gfx::ImageSkia top_left_corner = *rb.GetImageSkiaNamed(
285      IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_TOP_LEFT);
286  gfx::ImageSkia top_right_corner = *rb.GetImageSkiaNamed(
287      IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_TOP_RIGHT);
288  gfx::ImageSkia top_edge = *rb.GetImageSkiaNamed(
289      IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_TOP);
290  gfx::ImageSkia left_edge = *rb.GetImageSkiaNamed(
291      IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_LEFT);
292  gfx::ImageSkia right_edge = *rb.GetImageSkiaNamed(
293      IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_RIGHT);
294
295  int top_left_width = top_left_corner.width();
296  int top_left_height = top_left_corner.height();
297  canvas->DrawImageInt(top_left_corner, 0, 0);
298
299  int top_right_width = top_right_corner.width();
300  int top_right_height = top_right_corner.height();
301  canvas->DrawImageInt(top_right_corner,
302                       view_->width() - top_right_width,
303                       0);
304
305  canvas->TileImageInt(
306      top_edge,
307      top_left_width,
308      0,
309      view_->width() - top_left_width - top_right_width,
310      top_edge.height());
311
312  canvas->TileImageInt(left_edge,
313                       0,
314                       top_left_height,
315                       left_edge.width(),
316                       painted_height_ - top_left_height);
317
318  canvas->TileImageInt(right_edge,
319                       view_->width() - right_edge.width(),
320                       top_right_height,
321                       right_edge.width(),
322                       painted_height_ - top_right_height);
323}
324
325void BrowserHeaderPainterAsh::PaintTitleBar(gfx::Canvas* canvas) {
326  // The window icon is painted by its own views::View.
327  gfx::Rect title_bounds = GetTitleBounds();
328  title_bounds.set_x(view_->GetMirroredXForRect(title_bounds));
329  SkColor title_color = (frame_->IsMaximized() || frame_->IsFullscreen()) ?
330      kMaximizedWindowTitleTextColor : kRestoredWindowTitleTextColor;
331  canvas->DrawStringRectWithFlags(frame_->widget_delegate()->GetWindowTitle(),
332                                  BrowserFrame::GetTitleFontList(),
333                                  title_color,
334                                  title_bounds,
335                                  gfx::Canvas::NO_SUBPIXEL_RENDERING);
336}
337
338void BrowserHeaderPainterAsh::GetFrameImages(
339    Mode mode,
340    gfx::ImageSkia* frame_image,
341    gfx::ImageSkia* frame_overlay_image) const {
342  if (is_tabbed_) {
343    GetFrameImagesForTabbedBrowser(mode, frame_image, frame_overlay_image);
344  } else {
345    *frame_image = GetFrameImageForNonTabbedBrowser(mode);
346    *frame_overlay_image = gfx::ImageSkia();
347  }
348}
349
350void BrowserHeaderPainterAsh::GetFrameImagesForTabbedBrowser(
351    Mode mode,
352    gfx::ImageSkia* frame_image,
353    gfx::ImageSkia* frame_overlay_image) const {
354  int frame_image_id = 0;
355  int frame_overlay_image_id = 0;
356
357  ui::ThemeProvider* tp = frame_->GetThemeProvider();
358  if (tp->HasCustomImage(IDR_THEME_FRAME_OVERLAY) && !is_incognito_) {
359    frame_overlay_image_id = (mode == MODE_ACTIVE) ?
360        IDR_THEME_FRAME_OVERLAY : IDR_THEME_FRAME_OVERLAY_INACTIVE;
361  }
362
363  if (mode == MODE_ACTIVE) {
364    frame_image_id = is_incognito_ ?
365        IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME;
366  } else {
367    frame_image_id = is_incognito_ ?
368        IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE;
369  }
370
371  if ((frame_->IsMaximized() || frame_->IsFullscreen()) &&
372      !tp->HasCustomImage(IDR_THEME_FRAME) &&
373      !tp->HasCustomImage(frame_image_id) &&
374      frame_overlay_image_id == 0) {
375    *frame_image = *tp->GetImageSkiaNamed(
376        IDR_ASH_BROWSER_WINDOW_HEADER_BASE_MAXIMIZED);
377  } else {
378    *frame_image = *tp->GetImageSkiaNamed(frame_image_id);
379  }
380
381  *frame_overlay_image = (frame_overlay_image_id == 0) ?
382      gfx::ImageSkia() : *tp->GetImageSkiaNamed(frame_overlay_image_id);
383}
384
385gfx::ImageSkia BrowserHeaderPainterAsh::GetFrameImageForNonTabbedBrowser(
386    Mode mode) const {
387  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
388  if (frame_->IsMaximized() || frame_->IsFullscreen())
389    return *rb.GetImageSkiaNamed(IDR_ASH_BROWSER_WINDOW_HEADER_BASE_MAXIMIZED);
390
391  if (mode == MODE_ACTIVE) {
392    return *rb.GetImageSkiaNamed(is_incognito_ ?
393        IDR_ASH_BROWSER_WINDOW_HEADER_BASE_RESTORED_INCOGNITO_ACTIVE :
394        IDR_ASH_BROWSER_WINDOW_HEADER_BASE_RESTORED_ACTIVE);
395  }
396  return *rb.GetImageSkiaNamed(is_incognito_ ?
397      IDR_ASH_BROWSER_WINDOW_HEADER_BASE_RESTORED_INCOGNITO_INACTIVE :
398      IDR_ASH_BROWSER_WINDOW_HEADER_BASE_RESTORED_INACTIVE);
399}
400
401void BrowserHeaderPainterAsh::UpdateCaptionButtonImages() {
402  if (frame_->IsMaximized() || frame_->IsFullscreen()) {
403    caption_button_container_->SetButtonImages(
404        ash::CAPTION_BUTTON_ICON_MINIMIZE,
405        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_MINIMIZE,
406        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_MINIMIZE,
407        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H,
408        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P);
409    caption_button_container_->SetButtonImages(
410        ash::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE,
411        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_SIZE,
412        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_SIZE,
413        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H,
414        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P);
415    caption_button_container_->SetButtonImages(
416        ash::CAPTION_BUTTON_ICON_CLOSE,
417        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_CLOSE,
418        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_CLOSE,
419        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H,
420        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P);
421    caption_button_container_->SetButtonImages(
422        ash::CAPTION_BUTTON_ICON_LEFT_SNAPPED,
423        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_LEFT_SNAPPED,
424        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_LEFT_SNAPPED,
425        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H,
426        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P);
427    caption_button_container_->SetButtonImages(
428        ash::CAPTION_BUTTON_ICON_RIGHT_SNAPPED,
429        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_RIGHT_SNAPPED,
430        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_RIGHT_SNAPPED,
431        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H,
432        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P);
433  } else {
434    caption_button_container_->SetButtonImages(
435        ash::CAPTION_BUTTON_ICON_MINIMIZE,
436        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RESTORED_MINIMIZE,
437        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RESTORED_MINIMIZE,
438        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H,
439        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_P);
440    caption_button_container_->SetButtonImages(
441        ash::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE,
442        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RESTORED_SIZE,
443        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RESTORED_SIZE,
444        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H,
445        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_P);
446    caption_button_container_->SetButtonImages(
447        ash::CAPTION_BUTTON_ICON_CLOSE,
448        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RESTORED_CLOSE,
449        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RESTORED_CLOSE,
450        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H,
451        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_P);
452    caption_button_container_->SetButtonImages(
453        ash::CAPTION_BUTTON_ICON_LEFT_SNAPPED,
454        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RESTORED_LEFT_SNAPPED,
455        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RESTORED_LEFT_SNAPPED,
456        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H,
457        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_P);
458    caption_button_container_->SetButtonImages(
459        ash::CAPTION_BUTTON_ICON_RIGHT_SNAPPED,
460        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RESTORED_RIGHT_SNAPPED,
461        IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RESTORED_RIGHT_SNAPPED,
462        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H,
463        IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_P);
464  }
465}
466
467gfx::Rect BrowserHeaderPainterAsh::GetPaintedBounds() const {
468  return gfx::Rect(view_->width(), painted_height_);
469}
470
471gfx::Rect BrowserHeaderPainterAsh::GetTitleBounds() const {
472  return ash::HeaderPainterUtil::GetTitleBounds(window_icon_,
473      caption_button_container_, BrowserFrame::GetTitleFontList());
474}
475