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