browser_header_painter_ash.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
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_AURA_BROWSER_WINDOW_HEADER_SHADE_TOP_LEFT); 286 gfx::ImageSkia top_right_corner = *rb.GetImageSkiaNamed( 287 IDR_AURA_BROWSER_WINDOW_HEADER_SHADE_TOP_RIGHT); 288 gfx::ImageSkia top_edge = *rb.GetImageSkiaNamed( 289 IDR_AURA_BROWSER_WINDOW_HEADER_SHADE_TOP); 290 gfx::ImageSkia left_edge = *rb.GetImageSkiaNamed( 291 IDR_AURA_BROWSER_WINDOW_HEADER_SHADE_LEFT); 292 gfx::ImageSkia right_edge = *rb.GetImageSkiaNamed( 293 IDR_AURA_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_AURA_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_AURA_BROWSER_WINDOW_HEADER_BASE_MAXIMIZED); 390 391 if (mode == MODE_ACTIVE) { 392 return *rb.GetImageSkiaNamed(is_incognito_ ? 393 IDR_AURA_BROWSER_WINDOW_HEADER_BASE_RESTORED_INCOGNITO_ACTIVE : 394 IDR_AURA_BROWSER_WINDOW_HEADER_BASE_RESTORED_ACTIVE); 395 } 396 return *rb.GetImageSkiaNamed(is_incognito_ ? 397 IDR_AURA_BROWSER_WINDOW_HEADER_BASE_RESTORED_INCOGNITO_INACTIVE : 398 IDR_AURA_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_AURA_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_MINIMIZE, 406 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_MINIMIZE, 407 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H, 408 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P); 409 caption_button_container_->SetButtonImages( 410 ash::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE, 411 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_SIZE, 412 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_SIZE, 413 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H, 414 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P); 415 caption_button_container_->SetButtonImages( 416 ash::CAPTION_BUTTON_ICON_CLOSE, 417 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_CLOSE, 418 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_CLOSE, 419 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H, 420 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P); 421 caption_button_container_->SetButtonImages( 422 ash::CAPTION_BUTTON_ICON_LEFT_SNAPPED, 423 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_LEFT_SNAPPED, 424 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_LEFT_SNAPPED, 425 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H, 426 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P); 427 caption_button_container_->SetButtonImages( 428 ash::CAPTION_BUTTON_ICON_RIGHT_SNAPPED, 429 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_RIGHT_SNAPPED, 430 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZED_RIGHT_SNAPPED, 431 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H, 432 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P); 433 } else { 434 caption_button_container_->SetButtonImages( 435 ash::CAPTION_BUTTON_ICON_MINIMIZE, 436 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_RESTORED_MINIMIZE, 437 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_RESTORED_MINIMIZE, 438 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H, 439 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_P); 440 caption_button_container_->SetButtonImages( 441 ash::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE, 442 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_RESTORED_SIZE, 443 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_RESTORED_SIZE, 444 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H, 445 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_P); 446 caption_button_container_->SetButtonImages( 447 ash::CAPTION_BUTTON_ICON_CLOSE, 448 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_RESTORED_CLOSE, 449 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_RESTORED_CLOSE, 450 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H, 451 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_P); 452 caption_button_container_->SetButtonImages( 453 ash::CAPTION_BUTTON_ICON_LEFT_SNAPPED, 454 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_RESTORED_LEFT_SNAPPED, 455 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_RESTORED_LEFT_SNAPPED, 456 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H, 457 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_P); 458 caption_button_container_->SetButtonImages( 459 ash::CAPTION_BUTTON_ICON_RIGHT_SNAPPED, 460 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_RESTORED_RIGHT_SNAPPED, 461 IDR_AURA_BROWSER_WINDOW_CONTROL_ICON_RESTORED_RIGHT_SNAPPED, 462 IDR_AURA_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H, 463 IDR_AURA_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