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       caption_button_container_(NULL),
    118       painted_height_(0),
    119       initial_paint_(true),
    120       mode_(MODE_INACTIVE),
    121       activation_animation_(new gfx::SlideAnimation(this)) {
    122 }
    123 
    124 BrowserHeaderPainterAsh::~BrowserHeaderPainterAsh() {
    125 }
    126 
    127 void BrowserHeaderPainterAsh::Init(
    128     views::Widget* frame,
    129     BrowserView* browser_view,
    130     views::View* header_view,
    131     views::View* window_icon,
    132     ash::FrameCaptionButtonContainerView* caption_button_container) {
    133   DCHECK(frame);
    134   DCHECK(browser_view);
    135   DCHECK(header_view);
    136   // window_icon may be NULL.
    137   DCHECK(caption_button_container);
    138   frame_ = frame;
    139 
    140   is_tabbed_ = browser_view->browser()->is_type_tabbed();
    141   is_incognito_ = !browser_view->IsRegularOrGuestSession();
    142 
    143   view_ = header_view;
    144   window_icon_ = window_icon;
    145   caption_button_container_ = caption_button_container;
    146 }
    147 
    148 int BrowserHeaderPainterAsh::GetMinimumHeaderWidth() const {
    149   // Ensure we have enough space for the window icon and buttons. We allow
    150   // the title string to collapse to zero width.
    151   return GetTitleBounds().x() +
    152       caption_button_container_->GetMinimumSize().width();
    153 }
    154 
    155 void BrowserHeaderPainterAsh::PaintHeader(gfx::Canvas* canvas, Mode mode) {
    156   Mode old_mode = mode_;
    157   mode_ = mode;
    158 
    159   if (mode_ != old_mode) {
    160     if (!initial_paint_ &&
    161         ash::HeaderPainterUtil::CanAnimateActivation(frame_)) {
    162       activation_animation_->SetSlideDuration(kActivationCrossfadeDurationMs);
    163       if (mode_ == MODE_ACTIVE)
    164         activation_animation_->Show();
    165       else
    166         activation_animation_->Hide();
    167     } else {
    168       if (mode_ == MODE_ACTIVE)
    169         activation_animation_->Reset(1);
    170       else
    171         activation_animation_->Reset(0);
    172     }
    173     initial_paint_ = false;
    174   }
    175 
    176   int corner_radius = (frame_->IsMaximized() || frame_->IsFullscreen()) ?
    177       0 : ash::HeaderPainterUtil::GetTopCornerRadiusWhenRestored();
    178 
    179   int active_alpha = activation_animation_->CurrentValueBetween(0, 255);
    180   int inactive_alpha = 255 - active_alpha;
    181 
    182   SkPaint paint;
    183   if (inactive_alpha > 0) {
    184     if (active_alpha > 0)
    185       paint.setXfermodeMode(SkXfermode::kPlus_Mode);
    186 
    187     gfx::ImageSkia inactive_frame_image;
    188     gfx::ImageSkia inactive_frame_overlay_image;
    189     GetFrameImages(MODE_INACTIVE, &inactive_frame_image,
    190         &inactive_frame_overlay_image);
    191 
    192     paint.setAlpha(inactive_alpha);
    193     PaintFrameImagesInRoundRect(
    194         canvas,
    195         inactive_frame_image,
    196         inactive_frame_overlay_image,
    197         paint,
    198         GetPaintedBounds(),
    199         corner_radius,
    200         ash::HeaderPainterUtil::GetThemeBackgroundXInset());
    201   }
    202 
    203   if (active_alpha > 0) {
    204     gfx::ImageSkia active_frame_image;
    205     gfx::ImageSkia active_frame_overlay_image;
    206     GetFrameImages(MODE_ACTIVE, &active_frame_image,
    207         &active_frame_overlay_image);
    208 
    209     paint.setAlpha(active_alpha);
    210     PaintFrameImagesInRoundRect(
    211         canvas,
    212         active_frame_image,
    213         active_frame_overlay_image,
    214         paint,
    215         GetPaintedBounds(),
    216         corner_radius,
    217         ash::HeaderPainterUtil::GetThemeBackgroundXInset());
    218   }
    219 
    220   if (!frame_->IsMaximized() && !frame_->IsFullscreen())
    221     PaintHighlightForRestoredWindow(canvas);
    222   if (frame_->widget_delegate() &&
    223       frame_->widget_delegate()->ShouldShowWindowTitle()) {
    224     PaintTitleBar(canvas);
    225   }
    226 }
    227 
    228 void BrowserHeaderPainterAsh::LayoutHeader() {
    229   // Purposefully set |painted_height_| to an invalid value. We cannot use
    230   // |painted_height_| because the computation of |painted_height_| may depend
    231   // on having laid out the window controls.
    232   painted_height_ = -1;
    233 
    234   UpdateCaptionButtonImages();
    235   caption_button_container_->Layout();
    236 
    237   gfx::Size caption_button_container_size =
    238       caption_button_container_->GetPreferredSize();
    239   caption_button_container_->SetBounds(
    240       view_->width() - caption_button_container_size.width(),
    241       0,
    242       caption_button_container_size.width(),
    243       caption_button_container_size.height());
    244 
    245   if (window_icon_) {
    246     // Vertically center the window icon with respect to the caption button
    247     // container.
    248     int icon_size = ash::HeaderPainterUtil::GetDefaultIconSize();
    249     int icon_offset_y = (caption_button_container_->height() - icon_size) / 2;
    250     window_icon_->SetBounds(ash::HeaderPainterUtil::GetIconXOffset(),
    251         icon_offset_y, icon_size, icon_size);
    252   }
    253 }
    254 
    255 int BrowserHeaderPainterAsh::GetHeaderHeightForPainting() const {
    256   return painted_height_;
    257 }
    258 
    259 void BrowserHeaderPainterAsh::SetHeaderHeightForPainting(int height) {
    260   painted_height_ = height;
    261 }
    262 
    263 void BrowserHeaderPainterAsh::SchedulePaintForTitle() {
    264   view_->SchedulePaintInRect(GetTitleBounds());
    265 }
    266 
    267 ///////////////////////////////////////////////////////////////////////////////
    268 // gfx::AnimationDelegate overrides:
    269 
    270 void BrowserHeaderPainterAsh::AnimationProgressed(
    271     const gfx::Animation* animation) {
    272   view_->SchedulePaintInRect(GetPaintedBounds());
    273 }
    274 
    275 ///////////////////////////////////////////////////////////////////////////////
    276 // BrowserHeaderPainterAsh, private:
    277 
    278 void BrowserHeaderPainterAsh::PaintHighlightForRestoredWindow(
    279     gfx::Canvas* canvas) {
    280   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    281   gfx::ImageSkia top_left_corner = *rb.GetImageSkiaNamed(
    282       IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_TOP_LEFT);
    283   gfx::ImageSkia top_right_corner = *rb.GetImageSkiaNamed(
    284       IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_TOP_RIGHT);
    285   gfx::ImageSkia top_edge = *rb.GetImageSkiaNamed(
    286       IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_TOP);
    287   gfx::ImageSkia left_edge = *rb.GetImageSkiaNamed(
    288       IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_LEFT);
    289   gfx::ImageSkia right_edge = *rb.GetImageSkiaNamed(
    290       IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_RIGHT);
    291 
    292   int top_left_width = top_left_corner.width();
    293   int top_left_height = top_left_corner.height();
    294   canvas->DrawImageInt(top_left_corner, 0, 0);
    295 
    296   int top_right_width = top_right_corner.width();
    297   int top_right_height = top_right_corner.height();
    298   canvas->DrawImageInt(top_right_corner,
    299                        view_->width() - top_right_width,
    300                        0);
    301 
    302   canvas->TileImageInt(
    303       top_edge,
    304       top_left_width,
    305       0,
    306       view_->width() - top_left_width - top_right_width,
    307       top_edge.height());
    308 
    309   canvas->TileImageInt(left_edge,
    310                        0,
    311                        top_left_height,
    312                        left_edge.width(),
    313                        painted_height_ - top_left_height);
    314 
    315   canvas->TileImageInt(right_edge,
    316                        view_->width() - right_edge.width(),
    317                        top_right_height,
    318                        right_edge.width(),
    319                        painted_height_ - top_right_height);
    320 }
    321 
    322 void BrowserHeaderPainterAsh::PaintTitleBar(gfx::Canvas* canvas) {
    323   // The window icon is painted by its own views::View.
    324   gfx::Rect title_bounds = GetTitleBounds();
    325   title_bounds.set_x(view_->GetMirroredXForRect(title_bounds));
    326   canvas->DrawStringRectWithFlags(frame_->widget_delegate()->GetWindowTitle(),
    327                                   BrowserFrame::GetTitleFontList(),
    328                                   kWindowTitleTextColor,
    329                                   title_bounds,
    330                                   gfx::Canvas::NO_SUBPIXEL_RENDERING);
    331 }
    332 
    333 void BrowserHeaderPainterAsh::GetFrameImages(
    334     Mode mode,
    335     gfx::ImageSkia* frame_image,
    336     gfx::ImageSkia* frame_overlay_image) const {
    337   if (is_tabbed_) {
    338     GetFrameImagesForTabbedBrowser(mode, frame_image, frame_overlay_image);
    339   } else {
    340     *frame_image = GetFrameImageForNonTabbedBrowser(mode);
    341     *frame_overlay_image = gfx::ImageSkia();
    342   }
    343 }
    344 
    345 void BrowserHeaderPainterAsh::GetFrameImagesForTabbedBrowser(
    346     Mode mode,
    347     gfx::ImageSkia* frame_image,
    348     gfx::ImageSkia* frame_overlay_image) const {
    349   int frame_image_id = 0;
    350   int frame_overlay_image_id = 0;
    351 
    352   ui::ThemeProvider* tp = frame_->GetThemeProvider();
    353   if (tp->HasCustomImage(IDR_THEME_FRAME_OVERLAY) && !is_incognito_) {
    354     frame_overlay_image_id = (mode == MODE_ACTIVE) ?
    355         IDR_THEME_FRAME_OVERLAY : IDR_THEME_FRAME_OVERLAY_INACTIVE;
    356   }
    357 
    358   if (mode == MODE_ACTIVE) {
    359     frame_image_id = is_incognito_ ?
    360         IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME;
    361   } else {
    362     frame_image_id = is_incognito_ ?
    363         IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE;
    364   }
    365 
    366   *frame_image = *tp->GetImageSkiaNamed(frame_image_id);
    367   *frame_overlay_image = (frame_overlay_image_id == 0) ?
    368       gfx::ImageSkia() : *tp->GetImageSkiaNamed(frame_overlay_image_id);
    369 }
    370 
    371 gfx::ImageSkia BrowserHeaderPainterAsh::GetFrameImageForNonTabbedBrowser(
    372     Mode mode) const {
    373   // Request the images from the ResourceBundle (and not from the ThemeProvider)
    374   // in order to get the default non-themed assets.
    375   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    376   if (mode == MODE_ACTIVE) {
    377     return *rb.GetImageSkiaNamed(is_incognito_ ?
    378         IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME);
    379   }
    380   return *rb.GetImageSkiaNamed(is_incognito_ ?
    381       IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE);
    382 }
    383 
    384 void BrowserHeaderPainterAsh::UpdateCaptionButtonImages() {
    385   int hover_background_id = 0;
    386   int pressed_background_id = 0;
    387   if (frame_->IsMaximized() || frame_->IsFullscreen()) {
    388     hover_background_id =
    389         IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H;
    390     pressed_background_id =
    391         IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P;
    392   } else {
    393     hover_background_id =
    394         IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H;
    395     pressed_background_id =
    396         IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_P;
    397   }
    398   caption_button_container_->SetButtonImages(
    399       ash::CAPTION_BUTTON_ICON_MINIMIZE,
    400       IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MINIMIZE,
    401       IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MINIMIZE,
    402       hover_background_id,
    403       pressed_background_id);
    404   caption_button_container_->SetButtonImages(
    405       ash::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE,
    406       IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_SIZE,
    407       IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_SIZE,
    408       hover_background_id,
    409       pressed_background_id);
    410   caption_button_container_->SetButtonImages(
    411       ash::CAPTION_BUTTON_ICON_CLOSE,
    412       IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_CLOSE,
    413       IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_CLOSE,
    414       hover_background_id,
    415       pressed_background_id);
    416   caption_button_container_->SetButtonImages(
    417       ash::CAPTION_BUTTON_ICON_LEFT_SNAPPED,
    418       IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_LEFT_SNAPPED,
    419       IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_LEFT_SNAPPED,
    420       hover_background_id,
    421       pressed_background_id);
    422   caption_button_container_->SetButtonImages(
    423       ash::CAPTION_BUTTON_ICON_RIGHT_SNAPPED,
    424       IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RIGHT_SNAPPED,
    425       IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RIGHT_SNAPPED,
    426       hover_background_id,
    427       pressed_background_id);
    428 }
    429 
    430 gfx::Rect BrowserHeaderPainterAsh::GetPaintedBounds() const {
    431   return gfx::Rect(view_->width(), painted_height_);
    432 }
    433 
    434 gfx::Rect BrowserHeaderPainterAsh::GetTitleBounds() const {
    435   return ash::HeaderPainterUtil::GetTitleBounds(window_icon_,
    436       caption_button_container_, BrowserFrame::GetTitleFontList());
    437 }
    438