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