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