browser_header_painter_ash.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
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 29using views::Widget; 30 31namespace { 32// Color for the window title text. 33const SkColor kWindowTitleTextColor = SkColorSetRGB(40, 40, 40); 34// Duration of crossfade animation for activating and deactivating frame. 35const 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. 39void 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. 61void 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 111BrowserHeaderPainterAsh::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 124BrowserHeaderPainterAsh::~BrowserHeaderPainterAsh() { 125} 126 127void 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 148int 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 155void 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 228void 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 255int BrowserHeaderPainterAsh::GetHeaderHeightForPainting() const { 256 return painted_height_; 257} 258 259void BrowserHeaderPainterAsh::SetHeaderHeightForPainting(int height) { 260 painted_height_ = height; 261} 262 263void BrowserHeaderPainterAsh::SchedulePaintForTitle() { 264 view_->SchedulePaintInRect(GetTitleBounds()); 265} 266 267/////////////////////////////////////////////////////////////////////////////// 268// gfx::AnimationDelegate overrides: 269 270void BrowserHeaderPainterAsh::AnimationProgressed( 271 const gfx::Animation* animation) { 272 view_->SchedulePaintInRect(GetPaintedBounds()); 273} 274 275/////////////////////////////////////////////////////////////////////////////// 276// BrowserHeaderPainterAsh, private: 277 278void 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 322void 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 333void 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 345void 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 371gfx::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 384void 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 430gfx::Rect BrowserHeaderPainterAsh::GetPaintedBounds() const { 431 return gfx::Rect(view_->width(), painted_height_); 432} 433 434gfx::Rect BrowserHeaderPainterAsh::GetTitleBounds() const { 435 return ash::HeaderPainterUtil::GetTitleBounds(window_icon_, 436 caption_button_container_, BrowserFrame::GetTitleFontList()); 437} 438