// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/views/frame/browser_header_painter_ash.h" #include "ash/frame/caption_buttons/frame_caption_button_container_view.h" #include "ash/frame/header_painter_util.h" #include "base/logging.h" // DCHECK #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/views/frame/browser_frame.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "grit/theme_resources.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkPaint.h" #include "third_party/skia/include/core/SkPath.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/theme_provider.h" #include "ui/gfx/animation/slide_animation.h" #include "ui/gfx/canvas.h" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/rect.h" #include "ui/gfx/skia_util.h" #include "ui/views/view.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" using views::Widget; namespace { // Color for the window title text. const SkColor kWindowTitleTextColor = SkColorSetRGB(40, 40, 40); // Duration of crossfade animation for activating and deactivating frame. const int kActivationCrossfadeDurationMs = 200; // Tiles an image into an area, rounding the top corners. Samples |image| // starting |image_inset_x| pixels from the left of the image. void TileRoundRect(gfx::Canvas* canvas, const gfx::ImageSkia& image, const SkPaint& paint, const gfx::Rect& bounds, int top_left_corner_radius, int top_right_corner_radius, int image_inset_x) { SkRect rect = gfx::RectToSkRect(bounds); const SkScalar kTopLeftRadius = SkIntToScalar(top_left_corner_radius); const SkScalar kTopRightRadius = SkIntToScalar(top_right_corner_radius); SkScalar radii[8] = { kTopLeftRadius, kTopLeftRadius, // top-left kTopRightRadius, kTopRightRadius, // top-right 0, 0, // bottom-right 0, 0}; // bottom-left SkPath path; path.addRoundRect(rect, radii, SkPath::kCW_Direction); canvas->DrawImageInPath(image, -image_inset_x, 0, path, paint); } // Tiles |frame_image| and |frame_overlay_image| into an area, rounding the top // corners. void PaintFrameImagesInRoundRect(gfx::Canvas* canvas, const gfx::ImageSkia& frame_image, const gfx::ImageSkia& frame_overlay_image, const SkPaint& paint, const gfx::Rect& bounds, int corner_radius, int image_inset_x) { SkXfermode::Mode normal_mode; SkXfermode::AsMode(NULL, &normal_mode); // If |paint| is using an unusual SkXfermode::Mode (this is the case while // crossfading), we must create a new canvas to overlay |frame_image| and // |frame_overlay_image| using |normal_mode| and then paint the result // using the unusual mode. We try to avoid this because creating a new // browser-width canvas is expensive. bool fast_path = (frame_overlay_image.isNull() || SkXfermode::IsMode(paint.getXfermode(), normal_mode)); if (fast_path) { TileRoundRect(canvas, frame_image, paint, bounds, corner_radius, corner_radius, image_inset_x); if (!frame_overlay_image.isNull()) { // Adjust |bounds| such that |frame_overlay_image| is not tiled. gfx::Rect overlay_bounds = bounds; overlay_bounds.Intersect( gfx::Rect(bounds.origin(), frame_overlay_image.size())); int top_left_corner_radius = corner_radius; int top_right_corner_radius = corner_radius; if (overlay_bounds.width() < bounds.width() - corner_radius) top_right_corner_radius = 0; TileRoundRect(canvas, frame_overlay_image, paint, overlay_bounds, top_left_corner_radius, top_right_corner_radius, 0); } } else { gfx::Canvas temporary_canvas(bounds.size(), canvas->image_scale(), false); temporary_canvas.TileImageInt(frame_image, image_inset_x, 0, 0, 0, bounds.width(), bounds.height()); temporary_canvas.DrawImageInt(frame_overlay_image, 0, 0); TileRoundRect(canvas, gfx::ImageSkia(temporary_canvas.ExtractImageRep()), paint, bounds, corner_radius, corner_radius, 0); } } } // namespace /////////////////////////////////////////////////////////////////////////////// // BrowserHeaderPainterAsh, public: BrowserHeaderPainterAsh::BrowserHeaderPainterAsh() : frame_(NULL), is_tabbed_(false), is_incognito_(false), view_(NULL), window_icon_(NULL), caption_button_container_(NULL), painted_height_(0), initial_paint_(true), mode_(MODE_INACTIVE), activation_animation_(new gfx::SlideAnimation(this)) { } BrowserHeaderPainterAsh::~BrowserHeaderPainterAsh() { } void BrowserHeaderPainterAsh::Init( views::Widget* frame, BrowserView* browser_view, views::View* header_view, views::View* window_icon, ash::FrameCaptionButtonContainerView* caption_button_container) { DCHECK(frame); DCHECK(browser_view); DCHECK(header_view); // window_icon may be NULL. DCHECK(caption_button_container); frame_ = frame; is_tabbed_ = browser_view->browser()->is_type_tabbed(); is_incognito_ = !browser_view->IsRegularOrGuestSession(); view_ = header_view; window_icon_ = window_icon; caption_button_container_ = caption_button_container; } int BrowserHeaderPainterAsh::GetMinimumHeaderWidth() const { // Ensure we have enough space for the window icon and buttons. We allow // the title string to collapse to zero width. return GetTitleBounds().x() + caption_button_container_->GetMinimumSize().width(); } void BrowserHeaderPainterAsh::PaintHeader(gfx::Canvas* canvas, Mode mode) { Mode old_mode = mode_; mode_ = mode; if (mode_ != old_mode) { if (!initial_paint_ && ash::HeaderPainterUtil::CanAnimateActivation(frame_)) { activation_animation_->SetSlideDuration(kActivationCrossfadeDurationMs); if (mode_ == MODE_ACTIVE) activation_animation_->Show(); else activation_animation_->Hide(); } else { if (mode_ == MODE_ACTIVE) activation_animation_->Reset(1); else activation_animation_->Reset(0); } initial_paint_ = false; } int corner_radius = (frame_->IsMaximized() || frame_->IsFullscreen()) ? 0 : ash::HeaderPainterUtil::GetTopCornerRadiusWhenRestored(); int active_alpha = activation_animation_->CurrentValueBetween(0, 255); int inactive_alpha = 255 - active_alpha; SkPaint paint; if (inactive_alpha > 0) { if (active_alpha > 0) paint.setXfermodeMode(SkXfermode::kPlus_Mode); gfx::ImageSkia inactive_frame_image; gfx::ImageSkia inactive_frame_overlay_image; GetFrameImages(MODE_INACTIVE, &inactive_frame_image, &inactive_frame_overlay_image); paint.setAlpha(inactive_alpha); PaintFrameImagesInRoundRect( canvas, inactive_frame_image, inactive_frame_overlay_image, paint, GetPaintedBounds(), corner_radius, ash::HeaderPainterUtil::GetThemeBackgroundXInset()); } if (active_alpha > 0) { gfx::ImageSkia active_frame_image; gfx::ImageSkia active_frame_overlay_image; GetFrameImages(MODE_ACTIVE, &active_frame_image, &active_frame_overlay_image); paint.setAlpha(active_alpha); PaintFrameImagesInRoundRect( canvas, active_frame_image, active_frame_overlay_image, paint, GetPaintedBounds(), corner_radius, ash::HeaderPainterUtil::GetThemeBackgroundXInset()); } if (!frame_->IsMaximized() && !frame_->IsFullscreen()) PaintHighlightForRestoredWindow(canvas); if (frame_->widget_delegate() && frame_->widget_delegate()->ShouldShowWindowTitle()) { PaintTitleBar(canvas); } } void BrowserHeaderPainterAsh::LayoutHeader() { // Purposefully set |painted_height_| to an invalid value. We cannot use // |painted_height_| because the computation of |painted_height_| may depend // on having laid out the window controls. painted_height_ = -1; UpdateCaptionButtonImages(); caption_button_container_->Layout(); gfx::Size caption_button_container_size = caption_button_container_->GetPreferredSize(); caption_button_container_->SetBounds( view_->width() - caption_button_container_size.width(), 0, caption_button_container_size.width(), caption_button_container_size.height()); if (window_icon_) { // Vertically center the window icon with respect to the caption button // container. int icon_size = ash::HeaderPainterUtil::GetDefaultIconSize(); int icon_offset_y = (caption_button_container_->height() - icon_size) / 2; window_icon_->SetBounds(ash::HeaderPainterUtil::GetIconXOffset(), icon_offset_y, icon_size, icon_size); } } int BrowserHeaderPainterAsh::GetHeaderHeightForPainting() const { return painted_height_; } void BrowserHeaderPainterAsh::SetHeaderHeightForPainting(int height) { painted_height_ = height; } void BrowserHeaderPainterAsh::SchedulePaintForTitle() { view_->SchedulePaintInRect(GetTitleBounds()); } /////////////////////////////////////////////////////////////////////////////// // gfx::AnimationDelegate overrides: void BrowserHeaderPainterAsh::AnimationProgressed( const gfx::Animation* animation) { view_->SchedulePaintInRect(GetPaintedBounds()); } /////////////////////////////////////////////////////////////////////////////// // BrowserHeaderPainterAsh, private: void BrowserHeaderPainterAsh::PaintHighlightForRestoredWindow( gfx::Canvas* canvas) { ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); gfx::ImageSkia top_left_corner = *rb.GetImageSkiaNamed( IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_TOP_LEFT); gfx::ImageSkia top_right_corner = *rb.GetImageSkiaNamed( IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_TOP_RIGHT); gfx::ImageSkia top_edge = *rb.GetImageSkiaNamed( IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_TOP); gfx::ImageSkia left_edge = *rb.GetImageSkiaNamed( IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_LEFT); gfx::ImageSkia right_edge = *rb.GetImageSkiaNamed( IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_RIGHT); int top_left_width = top_left_corner.width(); int top_left_height = top_left_corner.height(); canvas->DrawImageInt(top_left_corner, 0, 0); int top_right_width = top_right_corner.width(); int top_right_height = top_right_corner.height(); canvas->DrawImageInt(top_right_corner, view_->width() - top_right_width, 0); canvas->TileImageInt( top_edge, top_left_width, 0, view_->width() - top_left_width - top_right_width, top_edge.height()); canvas->TileImageInt(left_edge, 0, top_left_height, left_edge.width(), painted_height_ - top_left_height); canvas->TileImageInt(right_edge, view_->width() - right_edge.width(), top_right_height, right_edge.width(), painted_height_ - top_right_height); } void BrowserHeaderPainterAsh::PaintTitleBar(gfx::Canvas* canvas) { // The window icon is painted by its own views::View. gfx::Rect title_bounds = GetTitleBounds(); title_bounds.set_x(view_->GetMirroredXForRect(title_bounds)); canvas->DrawStringRectWithFlags(frame_->widget_delegate()->GetWindowTitle(), BrowserFrame::GetTitleFontList(), kWindowTitleTextColor, title_bounds, gfx::Canvas::NO_SUBPIXEL_RENDERING); } void BrowserHeaderPainterAsh::GetFrameImages( Mode mode, gfx::ImageSkia* frame_image, gfx::ImageSkia* frame_overlay_image) const { if (is_tabbed_) { GetFrameImagesForTabbedBrowser(mode, frame_image, frame_overlay_image); } else { *frame_image = GetFrameImageForNonTabbedBrowser(mode); *frame_overlay_image = gfx::ImageSkia(); } } void BrowserHeaderPainterAsh::GetFrameImagesForTabbedBrowser( Mode mode, gfx::ImageSkia* frame_image, gfx::ImageSkia* frame_overlay_image) const { int frame_image_id = 0; int frame_overlay_image_id = 0; ui::ThemeProvider* tp = frame_->GetThemeProvider(); if (tp->HasCustomImage(IDR_THEME_FRAME_OVERLAY) && !is_incognito_) { frame_overlay_image_id = (mode == MODE_ACTIVE) ? IDR_THEME_FRAME_OVERLAY : IDR_THEME_FRAME_OVERLAY_INACTIVE; } if (mode == MODE_ACTIVE) { frame_image_id = is_incognito_ ? IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME; } else { frame_image_id = is_incognito_ ? IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE; } *frame_image = *tp->GetImageSkiaNamed(frame_image_id); *frame_overlay_image = (frame_overlay_image_id == 0) ? gfx::ImageSkia() : *tp->GetImageSkiaNamed(frame_overlay_image_id); } gfx::ImageSkia BrowserHeaderPainterAsh::GetFrameImageForNonTabbedBrowser( Mode mode) const { // Request the images from the ResourceBundle (and not from the ThemeProvider) // in order to get the default non-themed assets. ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); if (mode == MODE_ACTIVE) { return *rb.GetImageSkiaNamed(is_incognito_ ? IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME); } return *rb.GetImageSkiaNamed(is_incognito_ ? IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE); } void BrowserHeaderPainterAsh::UpdateCaptionButtonImages() { int hover_background_id = 0; int pressed_background_id = 0; if (frame_->IsMaximized() || frame_->IsFullscreen()) { hover_background_id = IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H; pressed_background_id = IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P; } else { hover_background_id = IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H; pressed_background_id = IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_P; } caption_button_container_->SetButtonImages( ash::CAPTION_BUTTON_ICON_MINIMIZE, IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MINIMIZE, IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MINIMIZE, hover_background_id, pressed_background_id); caption_button_container_->SetButtonImages( ash::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE, IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_SIZE, IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_SIZE, hover_background_id, pressed_background_id); caption_button_container_->SetButtonImages( ash::CAPTION_BUTTON_ICON_CLOSE, IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_CLOSE, IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_CLOSE, hover_background_id, pressed_background_id); caption_button_container_->SetButtonImages( ash::CAPTION_BUTTON_ICON_LEFT_SNAPPED, IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_LEFT_SNAPPED, IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_LEFT_SNAPPED, hover_background_id, pressed_background_id); caption_button_container_->SetButtonImages( ash::CAPTION_BUTTON_ICON_RIGHT_SNAPPED, IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RIGHT_SNAPPED, IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RIGHT_SNAPPED, hover_background_id, pressed_background_id); } gfx::Rect BrowserHeaderPainterAsh::GetPaintedBounds() const { return gfx::Rect(view_->width(), painted_height_); } gfx::Rect BrowserHeaderPainterAsh::GetTitleBounds() const { return ash::HeaderPainterUtil::GetTitleBounds(window_icon_, caption_button_container_, BrowserFrame::GetTitleFontList()); }