1 // Copyright (c) 2012 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/tabs/tab.h" 6 7 #include <limits> 8 9 #include "base/command_line.h" 10 #include "base/debug/alias.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "chrome/browser/defaults.h" 13 #include "chrome/browser/themes/theme_properties.h" 14 #include "chrome/browser/ui/browser.h" 15 #include "chrome/browser/ui/tab_contents/core_tab_helper.h" 16 #include "chrome/browser/ui/tabs/tab_resources.h" 17 #include "chrome/browser/ui/tabs/tab_utils.h" 18 #include "chrome/browser/ui/view_ids.h" 19 #include "chrome/browser/ui/views/tabs/tab_controller.h" 20 #include "chrome/browser/ui/views/theme_image_mapper.h" 21 #include "chrome/browser/ui/views/touch_uma/touch_uma.h" 22 #include "chrome/common/chrome_switches.h" 23 #include "grit/generated_resources.h" 24 #include "grit/theme_resources.h" 25 #include "grit/ui_resources.h" 26 #include "third_party/skia/include/effects/SkGradientShader.h" 27 #include "ui/base/accessibility/accessible_view_state.h" 28 #include "ui/base/l10n/l10n_util.h" 29 #include "ui/base/layout.h" 30 #include "ui/base/models/list_selection_model.h" 31 #include "ui/base/resource/resource_bundle.h" 32 #include "ui/base/theme_provider.h" 33 #include "ui/gfx/animation/animation_container.h" 34 #include "ui/gfx/animation/multi_animation.h" 35 #include "ui/gfx/animation/throb_animation.h" 36 #include "ui/gfx/canvas.h" 37 #include "ui/gfx/color_analysis.h" 38 #include "ui/gfx/favicon_size.h" 39 #include "ui/gfx/font.h" 40 #include "ui/gfx/image/image_skia_operations.h" 41 #include "ui/gfx/path.h" 42 #include "ui/gfx/rect_conversions.h" 43 #include "ui/gfx/skia_util.h" 44 #include "ui/gfx/text_elider.h" 45 #include "ui/views/border.h" 46 #include "ui/views/controls/button/image_button.h" 47 #include "ui/views/rect_based_targeting_utils.h" 48 #include "ui/views/widget/tooltip_manager.h" 49 #include "ui/views/widget/widget.h" 50 #include "ui/views/window/non_client_view.h" 51 52 #if defined(OS_WIN) 53 #include "win8/util/win8_util.h" 54 #endif 55 56 #if defined(USE_ASH) 57 #include "ui/aura/env.h" 58 #endif 59 60 namespace { 61 62 // Padding around the "content" of a tab, occupied by the tab border graphics. 63 64 int left_padding() { 65 static int value = -1; 66 if (value == -1) { 67 switch (ui::GetDisplayLayout()) { 68 case ui::LAYOUT_DESKTOP: 69 value = 22; 70 break; 71 case ui::LAYOUT_TOUCH: 72 value = 30; 73 break; 74 default: 75 NOTREACHED(); 76 } 77 } 78 return value; 79 } 80 81 int top_padding() { 82 static int value = -1; 83 if (value == -1) { 84 switch (ui::GetDisplayLayout()) { 85 case ui::LAYOUT_DESKTOP: 86 value = 7; 87 break; 88 case ui::LAYOUT_TOUCH: 89 value = 10; 90 break; 91 default: 92 NOTREACHED(); 93 } 94 } 95 return value; 96 } 97 98 int right_padding() { 99 static int value = -1; 100 if (value == -1) { 101 switch (ui::GetDisplayLayout()) { 102 case ui::LAYOUT_DESKTOP: 103 value = 17; 104 break; 105 case ui::LAYOUT_TOUCH: 106 value = 21; 107 break; 108 default: 109 NOTREACHED(); 110 } 111 } 112 return value; 113 } 114 115 int bottom_padding() { 116 static int value = -1; 117 if (value == -1) { 118 switch (ui::GetDisplayLayout()) { 119 case ui::LAYOUT_DESKTOP: 120 value = 5; 121 break; 122 case ui::LAYOUT_TOUCH: 123 value = 7; 124 break; 125 default: 126 NOTREACHED(); 127 } 128 } 129 return value; 130 } 131 132 // Height of the shadow at the top of the tab image assets. 133 int drop_shadow_height() { 134 static int value = -1; 135 if (value == -1) { 136 switch (ui::GetDisplayLayout()) { 137 case ui::LAYOUT_DESKTOP: 138 value = 4; 139 break; 140 case ui::LAYOUT_TOUCH: 141 value = 5; 142 break; 143 default: 144 NOTREACHED(); 145 } 146 } 147 return value; 148 } 149 150 // Size of icon used for throbber and favicon next to tab title. 151 int tab_icon_size() { 152 static int value = -1; 153 if (value == -1) { 154 switch (ui::GetDisplayLayout()) { 155 case ui::LAYOUT_DESKTOP: 156 value = gfx::kFaviconSize; 157 break; 158 case ui::LAYOUT_TOUCH: 159 value = 20; 160 break; 161 default: 162 NOTREACHED(); 163 } 164 } 165 return value; 166 } 167 168 // How long the pulse throb takes. 169 const int kPulseDurationMs = 200; 170 171 // Width of touch tabs. 172 static const int kTouchWidth = 120; 173 174 static const int kToolbarOverlap = 1; 175 static const int kFaviconTitleSpacing = 4; 176 // Additional vertical offset for title text relative to top of tab. 177 // Ash text rendering may be different than Windows. 178 static const int kTitleTextOffsetYAsh = 1; 179 static const int kTitleTextOffsetY = 0; 180 static const int kTitleCloseButtonSpacing = 3; 181 static const int kStandardTitleWidth = 175; 182 // Additional vertical offset for close button relative to top of tab. 183 // Ash needs this to match the text vertical position. 184 static const int kCloseButtonVertFuzzAsh = 1; 185 static const int kCloseButtonVertFuzz = 0; 186 // Additional horizontal offset for close button relative to title text. 187 static const int kCloseButtonHorzFuzz = 3; 188 189 // When a non-mini-tab becomes a mini-tab the width of the tab animates. If 190 // the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab 191 // is rendered as a normal tab. This is done to avoid having the title 192 // immediately disappear when transitioning a tab from normal to mini-tab. 193 static const int kMiniTabRendererAsNormalTabWidth = 194 browser_defaults::kMiniTabWidth + 30; 195 196 // How opaque to make the hover state (out of 1). 197 static const double kHoverOpacity = 0.33; 198 199 // Opacity for non-active selected tabs. 200 static const double kSelectedTabOpacity = .45; 201 202 // Selected (but not active) tabs have their throb value scaled down by this. 203 static const double kSelectedTabThrobScale = .5; 204 205 // Durations for the various parts of the mini tab title animation. 206 static const int kMiniTitleChangeAnimationDuration1MS = 1600; 207 static const int kMiniTitleChangeAnimationStart1MS = 0; 208 static const int kMiniTitleChangeAnimationEnd1MS = 1900; 209 static const int kMiniTitleChangeAnimationDuration2MS = 0; 210 static const int kMiniTitleChangeAnimationDuration3MS = 550; 211 static const int kMiniTitleChangeAnimationStart3MS = 150; 212 static const int kMiniTitleChangeAnimationEnd3MS = 800; 213 static const int kMiniTitleChangeAnimationIntervalMS = 40; 214 215 // Offset from the right edge for the start of the mini title change animation. 216 static const int kMiniTitleChangeInitialXOffset = 6; 217 218 // Radius of the radial gradient used for mini title change animation. 219 static const int kMiniTitleChangeGradientRadius = 20; 220 221 // Colors of the gradient used during the mini title change animation. 222 static const SkColor kMiniTitleChangeGradientColor1 = SK_ColorWHITE; 223 static const SkColor kMiniTitleChangeGradientColor2 = 224 SkColorSetARGB(0, 255, 255, 255); 225 226 // Max number of images to cache. This has to be at least two since rounding 227 // errors may lead to tabs in the same tabstrip having different sizes. 228 const size_t kMaxImageCacheSize = 4; 229 230 // Height of the miniature tab strip in immersive mode. 231 const int kImmersiveTabHeight = 3; 232 233 // Height of the small tab indicator rectangles in immersive mode. 234 const int kImmersiveBarHeight = 2; 235 236 // Color for active and inactive tabs in the immersive mode light strip. These 237 // should be a little brighter than the color of the normal art assets for tabs, 238 // which for active tabs is 230, 230, 230 and for inactive is 184, 184, 184. 239 const SkColor kImmersiveActiveTabColor = SkColorSetRGB(235, 235, 235); 240 const SkColor kImmersiveInactiveTabColor = SkColorSetRGB(190, 190, 190); 241 242 // The minimum opacity (out of 1) when a tab (either active or inactive) is 243 // throbbing in the immersive mode light strip. 244 const double kImmersiveTabMinThrobOpacity = 0.66; 245 246 // Number of steps in the immersive mode loading animation. 247 const int kImmersiveLoadingStepCount = 32; 248 249 const char kTabCloseButtonName[] = "TabCloseButton"; 250 251 void DrawIconAtLocation(gfx::Canvas* canvas, 252 const gfx::ImageSkia& image, 253 int image_offset, 254 int dst_x, 255 int dst_y, 256 int icon_width, 257 int icon_height, 258 bool filter, 259 const SkPaint& paint) { 260 // NOTE: the clipping is a work around for 69528, it shouldn't be necessary. 261 canvas->Save(); 262 canvas->ClipRect(gfx::Rect(dst_x, dst_y, icon_width, icon_height)); 263 canvas->DrawImageInt(image, 264 image_offset, 0, icon_width, icon_height, 265 dst_x, dst_y, icon_width, icon_height, 266 filter, paint); 267 canvas->Restore(); 268 } 269 270 // Draws the icon image at the center of |bounds|. 271 void DrawIconCenter(gfx::Canvas* canvas, 272 const gfx::ImageSkia& image, 273 int image_offset, 274 int icon_width, 275 int icon_height, 276 const gfx::Rect& bounds, 277 bool filter, 278 const SkPaint& paint) { 279 // Center the image within bounds. 280 int dst_x = bounds.x() - (icon_width - bounds.width()) / 2; 281 int dst_y = bounds.y() - (icon_height - bounds.height()) / 2; 282 DrawIconAtLocation(canvas, image, image_offset, dst_x, dst_y, icon_width, 283 icon_height, filter, paint); 284 } 285 286 chrome::HostDesktopType GetHostDesktopType(views::View* view) { 287 // Widget is NULL when tabs are detached. 288 views::Widget* widget = view->GetWidget(); 289 return chrome::GetHostDesktopTypeForNativeView( 290 widget ? widget->GetNativeView() : NULL); 291 } 292 293 } // namespace 294 295 //////////////////////////////////////////////////////////////////////////////// 296 // FaviconCrashAnimation 297 // 298 // A custom animation subclass to manage the favicon crash animation. 299 class Tab::FaviconCrashAnimation : public gfx::LinearAnimation, 300 public gfx::AnimationDelegate { 301 public: 302 explicit FaviconCrashAnimation(Tab* target) 303 : gfx::LinearAnimation(1000, 25, this), 304 target_(target) { 305 } 306 virtual ~FaviconCrashAnimation() {} 307 308 // gfx::Animation overrides: 309 virtual void AnimateToState(double state) OVERRIDE { 310 const double kHidingOffset = 27; 311 312 if (state < .5) { 313 target_->SetFaviconHidingOffset( 314 static_cast<int>(floor(kHidingOffset * 2.0 * state))); 315 } else { 316 target_->DisplayCrashedFavicon(); 317 target_->SetFaviconHidingOffset( 318 static_cast<int>( 319 floor(kHidingOffset - ((state - .5) * 2.0 * kHidingOffset)))); 320 } 321 } 322 323 // gfx::AnimationDelegate overrides: 324 virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE { 325 target_->SetFaviconHidingOffset(0); 326 } 327 328 private: 329 Tab* target_; 330 331 DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation); 332 }; 333 334 //////////////////////////////////////////////////////////////////////////////// 335 // TabCloseButton 336 // 337 // This is a Button subclass that causes middle clicks to be forwarded to the 338 // parent View by explicitly not handling them in OnMousePressed. 339 class Tab::TabCloseButton : public views::ImageButton { 340 public: 341 explicit TabCloseButton(Tab* tab) : views::ImageButton(tab), tab_(tab) {} 342 virtual ~TabCloseButton() {} 343 344 // Overridden from views::View. 345 virtual View* GetEventHandlerForRect(const gfx::Rect& rect) OVERRIDE { 346 if (!views::UsePointBasedTargeting(rect)) 347 return View::GetEventHandlerForRect(rect); 348 349 // Ignore the padding set on the button. 350 gfx::Rect contents_bounds = GetContentsBounds(); 351 contents_bounds.set_x(GetMirroredXForRect(contents_bounds)); 352 353 // TODO(tdanderson): Remove this ifdef if rect-based targeting 354 // is turned on by default. 355 #if defined(USE_ASH) 356 // Include the padding in hit-test for touch events. 357 if (aura::Env::GetInstance()->is_touch_down()) 358 contents_bounds = GetLocalBounds(); 359 #elif defined(OS_WIN) 360 // TODO(sky): Use local-bounds if a touch-point is active. 361 // http://crbug.com/145258 362 #endif 363 364 return contents_bounds.Intersects(rect) ? this : parent(); 365 } 366 367 // Overridden from views::View. 368 virtual View* GetTooltipHandlerForPoint(const gfx::Point& point) OVERRIDE { 369 // Tab close button has no children, so tooltip handler should be the same 370 // as the event handler. 371 // In addition, a hit test has to be performed for the point (as 372 // GetTooltipHandlerForPoint() is responsible for it). 373 if (!HitTestPoint(point)) 374 return NULL; 375 return GetEventHandlerForPoint(point); 376 } 377 378 virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE { 379 if (tab_->controller()) 380 tab_->controller()->OnMouseEventInTab(this, event); 381 382 bool handled = ImageButton::OnMousePressed(event); 383 // Explicitly mark midle-mouse clicks as non-handled to ensure the tab 384 // sees them. 385 return event.IsOnlyMiddleMouseButton() ? false : handled; 386 } 387 388 virtual void OnMouseMoved(const ui::MouseEvent& event) OVERRIDE { 389 if (tab_->controller()) 390 tab_->controller()->OnMouseEventInTab(this, event); 391 CustomButton::OnMouseMoved(event); 392 } 393 394 virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE { 395 if (tab_->controller()) 396 tab_->controller()->OnMouseEventInTab(this, event); 397 CustomButton::OnMouseReleased(event); 398 } 399 400 virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { 401 // Consume all gesture events here so that the parent (Tab) does not 402 // start consuming gestures. 403 ImageButton::OnGestureEvent(event); 404 event->SetHandled(); 405 } 406 407 virtual bool HasHitTestMask() const OVERRIDE { 408 return true; 409 } 410 411 virtual void GetHitTestMask(HitTestSource source, 412 gfx::Path* path) const OVERRIDE { 413 // Use the button's contents bounds (which does not include padding) 414 // and the hit test mask of our parent |tab_| to determine if the 415 // button is hidden behind another tab. 416 gfx::Path tab_mask; 417 tab_->GetHitTestMask(source, &tab_mask); 418 419 gfx::Rect button_bounds(GetContentsBounds()); 420 button_bounds.set_x(GetMirroredXForRect(button_bounds)); 421 gfx::RectF tab_bounds_f(gfx::SkRectToRectF(tab_mask.getBounds())); 422 views::View::ConvertRectToTarget(tab_, this, &tab_bounds_f); 423 gfx::Rect tab_bounds = gfx::ToEnclosingRect(tab_bounds_f); 424 425 // If either the top or bottom of the tab close button is clipped, 426 // do not consider these regions to be part of the button's bounds. 427 int top_overflow = tab_bounds.y() - button_bounds.y(); 428 int bottom_overflow = button_bounds.bottom() - tab_bounds.bottom(); 429 if (top_overflow > 0) 430 button_bounds.set_y(tab_bounds.y()); 431 else if (bottom_overflow > 0) 432 button_bounds.set_height(button_bounds.height() - bottom_overflow); 433 434 // If the hit test request is in response to a gesture, |path| should be 435 // empty unless the entire tab close button is visible to the user. Hit 436 // test requests in response to a mouse event should always set |path| 437 // to be the visible portion of the tab close button, even if it is 438 // partially hidden behind another tab. 439 path->reset(); 440 gfx::Rect intersection(gfx::IntersectRects(tab_bounds, button_bounds)); 441 if (!intersection.IsEmpty()) { 442 // TODO(tdanderson): Consider always returning the intersection if 443 // the non-rectangular shape of the tabs can be accounted for. 444 if (source == HIT_TEST_SOURCE_TOUCH && 445 !tab_bounds.Contains(button_bounds)) 446 return; 447 448 path->addRect(RectToSkRect(intersection)); 449 } 450 } 451 452 virtual const char* GetClassName() const OVERRIDE { 453 return kTabCloseButtonName; 454 } 455 456 private: 457 Tab* tab_; 458 459 DISALLOW_COPY_AND_ASSIGN(TabCloseButton); 460 }; 461 462 //////////////////////////////////////////////////////////////////////////////// 463 // ImageCacheEntry 464 465 Tab::ImageCacheEntry::ImageCacheEntry() 466 : resource_id(-1), 467 scale_factor(ui::SCALE_FACTOR_NONE) { 468 } 469 470 Tab::ImageCacheEntry::~ImageCacheEntry() {} 471 472 //////////////////////////////////////////////////////////////////////////////// 473 // Tab, statics: 474 475 // static 476 const char Tab::kViewClassName[] = "Tab"; 477 478 // static 479 Tab::TabImage Tab::tab_alpha_ = {0}; 480 Tab::TabImage Tab::tab_active_ = {0}; 481 Tab::TabImage Tab::tab_inactive_ = {0}; 482 // static 483 gfx::Font* Tab::font_ = NULL; 484 // static 485 int Tab::font_height_ = 0; 486 // static 487 Tab::ImageCache* Tab::image_cache_ = NULL; 488 489 //////////////////////////////////////////////////////////////////////////////// 490 // Tab, public: 491 492 Tab::Tab(TabController* controller) 493 : controller_(controller), 494 closing_(false), 495 dragging_(false), 496 favicon_hiding_offset_(0), 497 loading_animation_frame_(0), 498 immersive_loading_step_(0), 499 should_display_crashed_favicon_(false), 500 animating_media_state_(TAB_MEDIA_STATE_NONE), 501 theme_provider_(NULL), 502 tab_activated_with_last_gesture_begin_(false), 503 hover_controller_(this), 504 showing_icon_(false), 505 showing_media_indicator_(false), 506 showing_close_button_(false), 507 close_button_color_(0) { 508 InitTabResources(); 509 510 // So we get don't get enter/exit on children and don't prematurely stop the 511 // hover. 512 set_notify_enter_exit_on_child(true); 513 514 set_id(VIEW_ID_TAB); 515 516 // Add the Close Button. 517 close_button_ = new TabCloseButton(this); 518 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 519 close_button_->SetImage(views::CustomButton::STATE_NORMAL, 520 rb.GetImageSkiaNamed(IDR_CLOSE_1)); 521 close_button_->SetImage(views::CustomButton::STATE_HOVERED, 522 rb.GetImageSkiaNamed(IDR_CLOSE_1_H)); 523 close_button_->SetImage(views::CustomButton::STATE_PRESSED, 524 rb.GetImageSkiaNamed(IDR_CLOSE_1_P)); 525 close_button_->SetAccessibleName( 526 l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE)); 527 // Disable animation so that the red danger sign shows up immediately 528 // to help avoid mis-clicks. 529 close_button_->SetAnimationDuration(0); 530 AddChildView(close_button_); 531 532 set_context_menu_controller(this); 533 } 534 535 Tab::~Tab() { 536 } 537 538 void Tab::set_animation_container(gfx::AnimationContainer* container) { 539 animation_container_ = container; 540 hover_controller_.SetAnimationContainer(container); 541 } 542 543 bool Tab::IsActive() const { 544 return controller() ? controller()->IsActiveTab(this) : true; 545 } 546 547 bool Tab::IsSelected() const { 548 return controller() ? controller()->IsTabSelected(this) : true; 549 } 550 551 void Tab::SetData(const TabRendererData& data) { 552 if (data_.Equals(data)) 553 return; 554 555 TabRendererData old(data_); 556 data_ = data; 557 558 if (data_.IsCrashed()) { 559 if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation()) { 560 data_.media_state = TAB_MEDIA_STATE_NONE; 561 #if defined(OS_CHROMEOS) 562 // On Chrome OS, we reload killed tabs automatically when the user 563 // switches to them. Don't display animations for these unless they're 564 // selected (i.e. in the foreground) -- we won't reload these 565 // automatically since we don't want to get into a crash loop. 566 if (IsSelected() || 567 data_.crashed_status != base::TERMINATION_STATUS_PROCESS_WAS_KILLED) 568 StartCrashAnimation(); 569 #else 570 StartCrashAnimation(); 571 #endif 572 } 573 } else { 574 if (IsPerformingCrashAnimation()) 575 StopCrashAnimation(); 576 ResetCrashedFavicon(); 577 } 578 579 if (data_.media_state != old.media_state) { 580 if (data_.media_state != TAB_MEDIA_STATE_NONE) 581 animating_media_state_ = data_.media_state; 582 StartMediaIndicatorAnimation(); 583 } 584 585 if (old.mini != data_.mini) { 586 if (tab_animation_.get() && tab_animation_->is_animating()) { 587 tab_animation_->Stop(); 588 tab_animation_.reset(NULL); 589 } 590 } 591 592 DataChanged(old); 593 594 Layout(); 595 SchedulePaint(); 596 } 597 598 void Tab::UpdateLoadingAnimation(TabRendererData::NetworkState state) { 599 if (state == data_.network_state && 600 state == TabRendererData::NETWORK_STATE_NONE) { 601 // If the network state is none and hasn't changed, do nothing. Otherwise we 602 // need to advance the animation frame. 603 return; 604 } 605 606 TabRendererData::NetworkState old_state = data_.network_state; 607 data_.network_state = state; 608 AdvanceLoadingAnimation(old_state, state); 609 } 610 611 void Tab::StartPulse() { 612 gfx::ThrobAnimation* animation = new gfx::ThrobAnimation(this); 613 animation->SetSlideDuration(kPulseDurationMs); 614 if (animation_container_.get()) 615 animation->SetContainer(animation_container_.get()); 616 animation->StartThrobbing(std::numeric_limits<int>::max()); 617 tab_animation_.reset(animation); 618 } 619 620 void Tab::StopPulse() { 621 if (!tab_animation_.get()) 622 return; 623 tab_animation_->Stop(); 624 tab_animation_.reset(NULL); 625 } 626 627 void Tab::StartMiniTabTitleAnimation() { 628 // We can only do this animation if the tab is mini because we will 629 // upcast tab_animation back to MultiAnimation when we draw. 630 if (!data().mini) 631 return; 632 if (!tab_animation_.get()) { 633 gfx::MultiAnimation::Parts parts; 634 parts.push_back( 635 gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration1MS, 636 gfx::Tween::EASE_OUT)); 637 parts.push_back( 638 gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration2MS, 639 gfx::Tween::ZERO)); 640 parts.push_back( 641 gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration3MS, 642 gfx::Tween::EASE_IN)); 643 parts[0].start_time_ms = kMiniTitleChangeAnimationStart1MS; 644 parts[0].end_time_ms = kMiniTitleChangeAnimationEnd1MS; 645 parts[2].start_time_ms = kMiniTitleChangeAnimationStart3MS; 646 parts[2].end_time_ms = kMiniTitleChangeAnimationEnd3MS; 647 base::TimeDelta timeout = 648 base::TimeDelta::FromMilliseconds(kMiniTitleChangeAnimationIntervalMS); 649 gfx::MultiAnimation* animation = new gfx::MultiAnimation(parts, timeout); 650 if (animation_container_.get()) 651 animation->SetContainer(animation_container_.get()); 652 animation->set_delegate(this); 653 tab_animation_.reset(animation); 654 } 655 tab_animation_->Start(); 656 } 657 658 void Tab::StopMiniTabTitleAnimation() { 659 if (!tab_animation_.get()) 660 return; 661 tab_animation_->Stop(); 662 tab_animation_.reset(NULL); 663 } 664 665 // static 666 gfx::Size Tab::GetBasicMinimumUnselectedSize() { 667 InitTabResources(); 668 669 gfx::Size minimum_size; 670 minimum_size.set_width(left_padding() + right_padding()); 671 // Since we use image images, the real minimum height of the image is 672 // defined most accurately by the height of the end cap images. 673 minimum_size.set_height(tab_active_.image_l->height()); 674 return minimum_size; 675 } 676 677 gfx::Size Tab::GetMinimumUnselectedSize() { 678 return GetBasicMinimumUnselectedSize(); 679 } 680 681 // static 682 gfx::Size Tab::GetMinimumSelectedSize() { 683 gfx::Size minimum_size = GetBasicMinimumUnselectedSize(); 684 minimum_size.set_width( 685 left_padding() + gfx::kFaviconSize + right_padding()); 686 return minimum_size; 687 } 688 689 // static 690 gfx::Size Tab::GetStandardSize() { 691 gfx::Size standard_size = GetBasicMinimumUnselectedSize(); 692 standard_size.set_width( 693 standard_size.width() + kFaviconTitleSpacing + kStandardTitleWidth); 694 return standard_size; 695 } 696 697 // static 698 int Tab::GetTouchWidth() { 699 return kTouchWidth; 700 } 701 702 // static 703 int Tab::GetMiniWidth() { 704 return browser_defaults::kMiniTabWidth; 705 } 706 707 // static 708 int Tab::GetImmersiveHeight() { 709 return kImmersiveTabHeight; 710 } 711 712 //////////////////////////////////////////////////////////////////////////////// 713 // Tab, AnimationDelegate overrides: 714 715 void Tab::AnimationProgressed(const gfx::Animation* animation) { 716 // Ignore if the pulse animation is being performed on active tab because 717 // it repaints the same image. See |Tab::PaintTabBackground()|. 718 if (animation == tab_animation_.get() && IsActive()) 719 return; 720 SchedulePaint(); 721 } 722 723 void Tab::AnimationCanceled(const gfx::Animation* animation) { 724 if (media_indicator_animation_ == animation) 725 animating_media_state_ = data_.media_state; 726 SchedulePaint(); 727 } 728 729 void Tab::AnimationEnded(const gfx::Animation* animation) { 730 if (media_indicator_animation_ == animation) 731 animating_media_state_ = data_.media_state; 732 SchedulePaint(); 733 } 734 735 //////////////////////////////////////////////////////////////////////////////// 736 // Tab, views::ButtonListener overrides: 737 738 void Tab::ButtonPressed(views::Button* sender, const ui::Event& event) { 739 const CloseTabSource source = 740 (event.type() == ui::ET_MOUSE_RELEASED && 741 (event.flags() & ui::EF_FROM_TOUCH) == 0) ? CLOSE_TAB_FROM_MOUSE : 742 CLOSE_TAB_FROM_TOUCH; 743 DCHECK_EQ(close_button_, sender); 744 controller()->CloseTab(this, source); 745 if (event.type() == ui::ET_GESTURE_TAP) 746 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_TABCLOSE_TAP); 747 } 748 749 //////////////////////////////////////////////////////////////////////////////// 750 // Tab, views::ContextMenuController overrides: 751 752 void Tab::ShowContextMenuForView(views::View* source, 753 const gfx::Point& point, 754 ui::MenuSourceType source_type) { 755 if (controller() && !closing()) 756 controller()->ShowContextMenuForTab(this, point, source_type); 757 } 758 759 //////////////////////////////////////////////////////////////////////////////// 760 // Tab, views::View overrides: 761 762 void Tab::OnPaint(gfx::Canvas* canvas) { 763 // Don't paint if we're narrower than we can render correctly. (This should 764 // only happen during animations). 765 if (width() < GetMinimumUnselectedSize().width() && !data().mini) 766 return; 767 768 gfx::Rect clip; 769 if (controller()) { 770 if (!controller()->ShouldPaintTab(this, &clip)) 771 return; 772 if (!clip.IsEmpty()) { 773 canvas->Save(); 774 canvas->ClipRect(clip); 775 } 776 } 777 778 if (controller() && controller()->IsImmersiveStyle()) 779 PaintImmersiveTab(canvas); 780 else 781 PaintTab(canvas); 782 783 if (!clip.IsEmpty()) 784 canvas->Restore(); 785 } 786 787 void Tab::Layout() { 788 gfx::Rect lb = GetContentsBounds(); 789 if (lb.IsEmpty()) 790 return; 791 lb.Inset( 792 left_padding(), top_padding(), right_padding(), bottom_padding()); 793 794 // The height of the content of the Tab is the largest of the favicon, 795 // the title text and the close button graphic. 796 int content_height = std::max(tab_icon_size(), font_height_); 797 close_button_->set_border(NULL); 798 gfx::Size close_button_size(close_button_->GetPreferredSize()); 799 content_height = std::max(content_height, close_button_size.height()); 800 801 // Size the Favicon. 802 showing_icon_ = ShouldShowIcon(); 803 if (showing_icon_) { 804 // Use the size of the favicon as apps use a bigger favicon size. 805 int favicon_top = top_padding() + content_height / 2 - tab_icon_size() / 2; 806 int favicon_left = lb.x(); 807 favicon_bounds_.SetRect(favicon_left, favicon_top, 808 tab_icon_size(), tab_icon_size()); 809 MaybeAdjustLeftForMiniTab(&favicon_bounds_); 810 } else { 811 favicon_bounds_.SetRect(lb.x(), lb.y(), 0, 0); 812 } 813 814 // Size the Close button. 815 showing_close_button_ = ShouldShowCloseBox(); 816 const bool is_host_desktop_type_ash = 817 GetHostDesktopType(this) == chrome::HOST_DESKTOP_TYPE_ASH; 818 if (showing_close_button_) { 819 const int close_button_vert_fuzz = is_host_desktop_type_ash ? 820 kCloseButtonVertFuzzAsh : kCloseButtonVertFuzz; 821 int close_button_top = top_padding() + close_button_vert_fuzz + 822 (content_height - close_button_size.height()) / 2; 823 // If the ratio of the close button size to tab width exceeds the maximum. 824 // The close button should be as large as possible so that there is a larger 825 // hit-target for touch events. So the close button bounds extends to the 826 // edges of the tab. However, the larger hit-target should be active only 827 // for mouse events, and the close-image should show up in the right place. 828 // So a border is added to the button with necessary padding. The close 829 // button (BaseTab::TabCloseButton) makes sure the padding is a hit-target 830 // only for touch events. 831 int top_border = close_button_top; 832 int bottom_border = height() - (close_button_size.height() + top_border); 833 int left_border = kCloseButtonHorzFuzz; 834 int right_border = width() - (lb.width() + close_button_size.width() + 835 left_border); 836 close_button_->set_border(views::Border::CreateEmptyBorder(top_border, 837 left_border, bottom_border, right_border)); 838 close_button_->SetPosition(gfx::Point(lb.width(), 0)); 839 close_button_->SizeToPreferredSize(); 840 close_button_->SetVisible(true); 841 } else { 842 close_button_->SetBounds(0, 0, 0, 0); 843 close_button_->SetVisible(false); 844 } 845 846 showing_media_indicator_ = ShouldShowMediaIndicator(); 847 if (showing_media_indicator_) { 848 const gfx::Image& media_indicator_image = 849 chrome::GetTabMediaIndicatorImage(animating_media_state_); 850 media_indicator_bounds_.set_width(media_indicator_image.Width()); 851 media_indicator_bounds_.set_height(media_indicator_image.Height()); 852 media_indicator_bounds_.set_y( 853 top_padding() + 854 (content_height - media_indicator_bounds_.height()) / 2); 855 const int right = showing_close_button_ ? 856 close_button_->x() + close_button_->GetInsets().left() : lb.right(); 857 media_indicator_bounds_.set_x( 858 std::max(lb.x(), right - media_indicator_bounds_.width())); 859 MaybeAdjustLeftForMiniTab(&media_indicator_bounds_); 860 } else { 861 media_indicator_bounds_.SetRect(lb.x(), lb.y(), 0, 0); 862 } 863 864 const int title_text_offset = is_host_desktop_type_ash ? 865 kTitleTextOffsetYAsh : kTitleTextOffsetY; 866 int title_left = favicon_bounds_.right() + kFaviconTitleSpacing; 867 int title_top = top_padding() + title_text_offset + 868 (content_height - font_height_) / 2; 869 // Size the Title text to fill the remaining space. 870 if (!data().mini || width() >= kMiniTabRendererAsNormalTabWidth) { 871 // If the user has big fonts, the title will appear rendered too far down 872 // on the y-axis if we use the regular top padding, so we need to adjust it 873 // so that the text appears centered. 874 gfx::Size minimum_size = GetMinimumUnselectedSize(); 875 int text_height = title_top + font_height_ + bottom_padding(); 876 if (text_height > minimum_size.height()) 877 title_top -= (text_height - minimum_size.height()) / 2; 878 879 int title_width; 880 if (showing_media_indicator_) { 881 title_width = media_indicator_bounds_.x() - kTitleCloseButtonSpacing - 882 title_left; 883 } else if (close_button_->visible()) { 884 // The close button has an empty border with some padding (see details 885 // above where the close-button's bounds is set). Allow the title to 886 // overlap the empty padding. 887 title_width = close_button_->x() + close_button_->GetInsets().left() - 888 kTitleCloseButtonSpacing - title_left; 889 } else { 890 title_width = lb.width() - title_left; 891 } 892 title_width = std::max(title_width, 0); 893 title_bounds_.SetRect(title_left, title_top, title_width, font_height_); 894 } else { 895 title_bounds_.SetRect(title_left, title_top, 0, 0); 896 } 897 898 // Certain UI elements within the Tab (the favicon, etc.) are not represented 899 // as child Views (which is the preferred method). Instead, these UI elements 900 // are drawn directly on the canvas from within Tab::OnPaint(). The Tab's 901 // child Views (for example, the Tab's close button which is a views::Button 902 // instance) are automatically mirrored by the mirroring infrastructure in 903 // views. The elements Tab draws directly on the canvas need to be manually 904 // mirrored if the View's layout is right-to-left. 905 title_bounds_.set_x(GetMirroredXForRect(title_bounds_)); 906 } 907 908 void Tab::OnThemeChanged() { 909 LoadTabImages(); 910 } 911 912 const char* Tab::GetClassName() const { 913 return kViewClassName; 914 } 915 916 bool Tab::HasHitTestMask() const { 917 return true; 918 } 919 920 void Tab::GetHitTestMask(HitTestSource source, gfx::Path* path) const { 921 // When the window is maximized we don't want to shave off the edges or top 922 // shadow of the tab, such that the user can click anywhere along the top 923 // edge of the screen to select a tab. Ditto for immersive fullscreen. 924 const views::Widget* widget = GetWidget(); 925 bool include_top_shadow = 926 widget && (widget->IsMaximized() || widget->IsFullscreen()); 927 TabResources::GetHitTestMask(width(), height(), include_top_shadow, path); 928 929 // It is possible for a portion of the tab to be occluded if tabs are 930 // stacked, so modify the hit test mask to only include the visible 931 // region of the tab. 932 if (controller()) { 933 gfx::Rect clip; 934 controller()->ShouldPaintTab(this, &clip); 935 if (clip.size().GetArea()) { 936 SkRect intersection(path->getBounds()); 937 intersection.intersect(RectToSkRect(clip)); 938 path->reset(); 939 path->addRect(intersection); 940 } 941 } 942 } 943 944 bool Tab::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const { 945 // TODO(miu): Rectify inconsistent tooltip behavior. http://crbug.com/310947 946 947 if (data_.media_state != TAB_MEDIA_STATE_NONE) { 948 *tooltip = chrome::AssembleTabTooltipText(data_.title, data_.media_state); 949 return true; 950 } 951 952 if (data_.title.empty()) 953 return false; 954 955 // Only show the tooltip if the title is truncated. 956 if (font_->GetStringWidth(data_.title) > GetTitleBounds().width()) { 957 *tooltip = data_.title; 958 return true; 959 } 960 return false; 961 } 962 963 bool Tab::GetTooltipTextOrigin(const gfx::Point& p, gfx::Point* origin) const { 964 origin->set_x(title_bounds_.x() + 10); 965 origin->set_y(-views::TooltipManager::GetTooltipHeight() - 4); 966 return true; 967 } 968 969 ui::ThemeProvider* Tab::GetThemeProvider() const { 970 ui::ThemeProvider* tp = View::GetThemeProvider(); 971 return tp ? tp : theme_provider_; 972 } 973 974 bool Tab::OnMousePressed(const ui::MouseEvent& event) { 975 if (!controller()) 976 return false; 977 978 controller()->OnMouseEventInTab(this, event); 979 980 // Allow a right click from touch to drag, which corresponds to a long click. 981 if (event.IsOnlyLeftMouseButton() || 982 (event.IsOnlyRightMouseButton() && event.flags() & ui::EF_FROM_TOUCH)) { 983 ui::ListSelectionModel original_selection; 984 original_selection.Copy(controller()->GetSelectionModel()); 985 // Changing the selection may cause our bounds to change. If that happens 986 // the location of the event may no longer be valid. Create a copy of the 987 // event in the parents coordinate, which won't change, and recreate an 988 // event after changing so the coordinates are correct. 989 ui::MouseEvent event_in_parent(event, static_cast<View*>(this), parent()); 990 if (controller()->SupportsMultipleSelection()) { 991 if (event.IsShiftDown() && event.IsControlDown()) { 992 controller()->AddSelectionFromAnchorTo(this); 993 } else if (event.IsShiftDown()) { 994 controller()->ExtendSelectionTo(this); 995 } else if (event.IsControlDown()) { 996 controller()->ToggleSelected(this); 997 if (!IsSelected()) { 998 // Don't allow dragging non-selected tabs. 999 return false; 1000 } 1001 } else if (!IsSelected()) { 1002 controller()->SelectTab(this); 1003 } 1004 } else if (!IsSelected()) { 1005 controller()->SelectTab(this); 1006 } 1007 ui::MouseEvent cloned_event(event_in_parent, parent(), 1008 static_cast<View*>(this)); 1009 controller()->MaybeStartDrag(this, cloned_event, original_selection); 1010 } 1011 return true; 1012 } 1013 1014 bool Tab::OnMouseDragged(const ui::MouseEvent& event) { 1015 if (controller()) 1016 controller()->ContinueDrag(this, event); 1017 return true; 1018 } 1019 1020 void Tab::OnMouseReleased(const ui::MouseEvent& event) { 1021 if (!controller()) 1022 return; 1023 1024 controller()->OnMouseEventInTab(this, event); 1025 1026 // Notify the drag helper that we're done with any potential drag operations. 1027 // Clean up the drag helper, which is re-created on the next mouse press. 1028 // In some cases, ending the drag will schedule the tab for destruction; if 1029 // so, bail immediately, since our members are already dead and we shouldn't 1030 // do anything else except drop the tab where it is. 1031 if (controller()->EndDrag(END_DRAG_COMPLETE)) 1032 return; 1033 1034 // Close tab on middle click, but only if the button is released over the tab 1035 // (normal windows behavior is to discard presses of a UI element where the 1036 // releases happen off the element). 1037 if (event.IsMiddleMouseButton()) { 1038 if (HitTestPoint(event.location())) { 1039 controller()->CloseTab(this, CLOSE_TAB_FROM_MOUSE); 1040 } else if (closing_) { 1041 // We're animating closed and a middle mouse button was pushed on us but 1042 // we don't contain the mouse anymore. We assume the user is clicking 1043 // quicker than the animation and we should close the tab that falls under 1044 // the mouse. 1045 Tab* closest_tab = controller()->GetTabAt(this, event.location()); 1046 if (closest_tab) 1047 controller()->CloseTab(closest_tab, CLOSE_TAB_FROM_MOUSE); 1048 } 1049 } else if (event.IsOnlyLeftMouseButton() && !event.IsShiftDown() && 1050 !event.IsControlDown()) { 1051 // If the tab was already selected mouse pressed doesn't change the 1052 // selection. Reset it now to handle the case where multiple tabs were 1053 // selected. 1054 controller()->SelectTab(this); 1055 } 1056 } 1057 1058 void Tab::OnMouseCaptureLost() { 1059 if (controller()) 1060 controller()->EndDrag(END_DRAG_CAPTURE_LOST); 1061 } 1062 1063 void Tab::OnMouseEntered(const ui::MouseEvent& event) { 1064 hover_controller_.Show(views::GlowHoverController::SUBTLE); 1065 } 1066 1067 void Tab::OnMouseMoved(const ui::MouseEvent& event) { 1068 hover_controller_.SetLocation(event.location()); 1069 if (controller()) 1070 controller()->OnMouseEventInTab(this, event); 1071 } 1072 1073 void Tab::OnMouseExited(const ui::MouseEvent& event) { 1074 hover_controller_.Hide(); 1075 } 1076 1077 void Tab::OnGestureEvent(ui::GestureEvent* event) { 1078 if (!controller()) { 1079 event->SetHandled(); 1080 return; 1081 } 1082 1083 switch (event->type()) { 1084 case ui::ET_GESTURE_BEGIN: { 1085 if (event->details().touch_points() != 1) 1086 return; 1087 1088 // See comment in OnMousePressed() as to why we copy the event. 1089 ui::GestureEvent event_in_parent(*event, static_cast<View*>(this), 1090 parent()); 1091 ui::ListSelectionModel original_selection; 1092 original_selection.Copy(controller()->GetSelectionModel()); 1093 tab_activated_with_last_gesture_begin_ = !IsActive(); 1094 if (!IsSelected()) 1095 controller()->SelectTab(this); 1096 gfx::Point loc(event->location()); 1097 views::View::ConvertPointToScreen(this, &loc); 1098 ui::GestureEvent cloned_event(event_in_parent, parent(), 1099 static_cast<View*>(this)); 1100 controller()->MaybeStartDrag(this, cloned_event, original_selection); 1101 break; 1102 } 1103 1104 case ui::ET_GESTURE_END: 1105 controller()->EndDrag(END_DRAG_COMPLETE); 1106 break; 1107 1108 case ui::ET_GESTURE_SCROLL_UPDATE: 1109 controller()->ContinueDrag(this, *event); 1110 break; 1111 1112 default: 1113 break; 1114 } 1115 event->SetHandled(); 1116 } 1117 1118 void Tab::GetAccessibleState(ui::AccessibleViewState* state) { 1119 state->role = ui::AccessibilityTypes::ROLE_PAGETAB; 1120 state->name = data_.title; 1121 } 1122 1123 //////////////////////////////////////////////////////////////////////////////// 1124 // Tab, private 1125 1126 const gfx::Rect& Tab::GetTitleBounds() const { 1127 return title_bounds_; 1128 } 1129 1130 const gfx::Rect& Tab::GetIconBounds() const { 1131 return favicon_bounds_; 1132 } 1133 1134 void Tab::MaybeAdjustLeftForMiniTab(gfx::Rect* bounds) const { 1135 if (!data().mini || width() >= kMiniTabRendererAsNormalTabWidth) 1136 return; 1137 const int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth(); 1138 const int ideal_delta = width() - GetMiniWidth(); 1139 const int ideal_x = (GetMiniWidth() - bounds->width()) / 2; 1140 bounds->set_x(bounds->x() + static_cast<int>( 1141 (1 - static_cast<float>(ideal_delta) / static_cast<float>(mini_delta)) * 1142 (ideal_x - bounds->x()))); 1143 } 1144 1145 void Tab::DataChanged(const TabRendererData& old) { 1146 if (data().blocked == old.blocked) 1147 return; 1148 1149 if (data().blocked) 1150 StartPulse(); 1151 else 1152 StopPulse(); 1153 } 1154 1155 void Tab::PaintTab(gfx::Canvas* canvas) { 1156 // See if the model changes whether the icons should be painted. 1157 const bool show_icon = ShouldShowIcon(); 1158 const bool show_media_indicator = ShouldShowMediaIndicator(); 1159 const bool show_close_button = ShouldShowCloseBox(); 1160 if (show_icon != showing_icon_ || 1161 show_media_indicator != showing_media_indicator_ || 1162 show_close_button != showing_close_button_) { 1163 Layout(); 1164 } 1165 1166 PaintTabBackground(canvas); 1167 1168 SkColor title_color = GetThemeProvider()-> 1169 GetColor(IsSelected() ? 1170 ThemeProperties::COLOR_TAB_TEXT : 1171 ThemeProperties::COLOR_BACKGROUND_TAB_TEXT); 1172 1173 if (!data().mini || width() > kMiniTabRendererAsNormalTabWidth) 1174 PaintTitle(canvas, title_color); 1175 1176 if (show_icon) 1177 PaintIcon(canvas); 1178 1179 if (show_media_indicator) 1180 PaintMediaIndicator(canvas); 1181 1182 // If the close button color has changed, generate a new one. 1183 if (!close_button_color_ || title_color != close_button_color_) { 1184 close_button_color_ = title_color; 1185 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 1186 close_button_->SetBackground(close_button_color_, 1187 rb.GetImageSkiaNamed(IDR_CLOSE_1), 1188 rb.GetImageSkiaNamed(IDR_CLOSE_1_MASK)); 1189 } 1190 } 1191 1192 void Tab::PaintImmersiveTab(gfx::Canvas* canvas) { 1193 // Use transparency for the draw-attention animation. 1194 int alpha = 255; 1195 if (tab_animation_ && 1196 tab_animation_->is_animating() && 1197 !data().mini) { 1198 alpha = tab_animation_->CurrentValueBetween( 1199 255, static_cast<int>(255 * kImmersiveTabMinThrobOpacity)); 1200 } 1201 1202 // Draw a gray rectangle to represent the tab. This works for mini-tabs as 1203 // well as regular ones. The active tab has a brigher bar. 1204 SkColor color = 1205 IsActive() ? kImmersiveActiveTabColor : kImmersiveInactiveTabColor; 1206 gfx::Rect bar_rect = GetImmersiveBarRect(); 1207 canvas->FillRect(bar_rect, SkColorSetA(color, alpha)); 1208 1209 // Paint network activity indicator. 1210 // TODO(jamescook): Replace this placeholder animation with a real one. 1211 // For now, let's go with a Cylon eye effect, but in blue. 1212 if (data().network_state != TabRendererData::NETWORK_STATE_NONE) { 1213 const SkColor kEyeColor = SkColorSetARGB(alpha, 71, 138, 217); 1214 int eye_width = bar_rect.width() / 3; 1215 int eye_offset = bar_rect.width() * immersive_loading_step_ / 1216 kImmersiveLoadingStepCount; 1217 if (eye_offset + eye_width < bar_rect.width()) { 1218 // Draw a single indicator strip because it fits inside |bar_rect|. 1219 gfx::Rect eye_rect( 1220 bar_rect.x() + eye_offset, 0, eye_width, kImmersiveBarHeight); 1221 canvas->FillRect(eye_rect, kEyeColor); 1222 } else { 1223 // Draw two indicators to simulate the eye "wrapping around" to the left 1224 // side. The first part fills the remainder of the bar. 1225 int right_eye_width = bar_rect.width() - eye_offset; 1226 gfx::Rect right_eye_rect( 1227 bar_rect.x() + eye_offset, 0, right_eye_width, kImmersiveBarHeight); 1228 canvas->FillRect(right_eye_rect, kEyeColor); 1229 // The second part parts the remaining |eye_width| on the left. 1230 int left_eye_width = eye_offset + eye_width - bar_rect.width(); 1231 gfx::Rect left_eye_rect( 1232 bar_rect.x(), 0, left_eye_width, kImmersiveBarHeight); 1233 canvas->FillRect(left_eye_rect, kEyeColor); 1234 } 1235 } 1236 } 1237 1238 void Tab::PaintTabBackground(gfx::Canvas* canvas) { 1239 if (IsActive()) { 1240 PaintActiveTabBackground(canvas); 1241 } else { 1242 if (tab_animation_.get() && 1243 tab_animation_->is_animating() && 1244 data().mini) { 1245 gfx::MultiAnimation* animation = 1246 static_cast<gfx::MultiAnimation*>(tab_animation_.get()); 1247 PaintInactiveTabBackgroundWithTitleChange(canvas, animation); 1248 } else { 1249 PaintInactiveTabBackground(canvas); 1250 } 1251 1252 double throb_value = GetThrobValue(); 1253 if (throb_value > 0) { 1254 canvas->SaveLayerAlpha(static_cast<int>(throb_value * 0xff), 1255 GetLocalBounds()); 1256 PaintActiveTabBackground(canvas); 1257 canvas->Restore(); 1258 } 1259 } 1260 } 1261 1262 void Tab::PaintInactiveTabBackgroundWithTitleChange( 1263 gfx::Canvas* canvas, 1264 gfx::MultiAnimation* animation) { 1265 // Render the inactive tab background. We'll use this for clipping. 1266 gfx::Canvas background_canvas(size(), canvas->image_scale(), false); 1267 PaintInactiveTabBackground(&background_canvas); 1268 1269 gfx::ImageSkia background_image(background_canvas.ExtractImageRep()); 1270 1271 // Draw a radial gradient to hover_canvas. 1272 gfx::Canvas hover_canvas(size(), canvas->image_scale(), false); 1273 int radius = kMiniTitleChangeGradientRadius; 1274 int x0 = width() + radius - kMiniTitleChangeInitialXOffset; 1275 int x1 = radius; 1276 int x2 = -radius; 1277 int x; 1278 if (animation->current_part_index() == 0) { 1279 x = animation->CurrentValueBetween(x0, x1); 1280 } else if (animation->current_part_index() == 1) { 1281 x = x1; 1282 } else { 1283 x = animation->CurrentValueBetween(x1, x2); 1284 } 1285 SkPoint center_point; 1286 center_point.iset(x, 0); 1287 SkColor colors[2] = { kMiniTitleChangeGradientColor1, 1288 kMiniTitleChangeGradientColor2 }; 1289 skia::RefPtr<SkShader> shader = skia::AdoptRef( 1290 SkGradientShader::CreateRadial( 1291 center_point, SkIntToScalar(radius), colors, NULL, 2, 1292 SkShader::kClamp_TileMode)); 1293 SkPaint paint; 1294 paint.setShader(shader.get()); 1295 hover_canvas.DrawRect(gfx::Rect(x - radius, -radius, radius * 2, radius * 2), 1296 paint); 1297 1298 // Draw the radial gradient clipped to the background into hover_image. 1299 gfx::ImageSkia hover_image = gfx::ImageSkiaOperations::CreateMaskedImage( 1300 gfx::ImageSkia(hover_canvas.ExtractImageRep()), background_image); 1301 1302 // Draw the tab background to the canvas. 1303 canvas->DrawImageInt(background_image, 0, 0); 1304 1305 // And then the gradient on top of that. 1306 if (animation->current_part_index() == 2) { 1307 uint8 alpha = animation->CurrentValueBetween(255, 0); 1308 canvas->DrawImageInt(hover_image, 0, 0, alpha); 1309 } else { 1310 canvas->DrawImageInt(hover_image, 0, 0); 1311 } 1312 } 1313 1314 void Tab::PaintInactiveTabBackground(gfx::Canvas* canvas) { 1315 int tab_id; 1316 int frame_id; 1317 views::Widget* widget = GetWidget(); 1318 GetTabIdAndFrameId(widget, &tab_id, &frame_id); 1319 1320 // Explicitly map the id so we cache correctly. 1321 const chrome::HostDesktopType host_desktop_type = GetHostDesktopType(this); 1322 tab_id = chrome::MapThemeImage(host_desktop_type, tab_id); 1323 1324 // HasCustomImage() is only true if the theme provides the image. However, 1325 // even if the theme does not provide a tab background, the theme machinery 1326 // will make one if given a frame image. 1327 ui::ThemeProvider* theme_provider = GetThemeProvider(); 1328 const bool theme_provided_image = theme_provider->HasCustomImage(tab_id) || 1329 (frame_id != 0 && theme_provider->HasCustomImage(frame_id)); 1330 1331 const bool can_cache = !theme_provided_image && 1332 !hover_controller_.ShouldDraw(); 1333 1334 if (can_cache) { 1335 ui::ScaleFactor scale_factor = 1336 ui::GetSupportedScaleFactor(canvas->image_scale()); 1337 gfx::ImageSkia cached_image(GetCachedImage(tab_id, size(), scale_factor)); 1338 if (cached_image.width() == 0) { 1339 gfx::Canvas tmp_canvas(size(), canvas->image_scale(), false); 1340 PaintInactiveTabBackgroundUsingResourceId(&tmp_canvas, tab_id); 1341 cached_image = gfx::ImageSkia(tmp_canvas.ExtractImageRep()); 1342 SetCachedImage(tab_id, scale_factor, cached_image); 1343 } 1344 canvas->DrawImageInt(cached_image, 0, 0); 1345 } else { 1346 PaintInactiveTabBackgroundUsingResourceId(canvas, tab_id); 1347 } 1348 } 1349 1350 void Tab::PaintInactiveTabBackgroundUsingResourceId(gfx::Canvas* canvas, 1351 int tab_id) { 1352 // WARNING: the inactive tab background may be cached. If you change what it 1353 // is drawn from you may need to update whether it can be cached. 1354 1355 // The tab image needs to be lined up with the background image 1356 // so that it feels partially transparent. These offsets represent the tab 1357 // position within the frame background image. 1358 int offset = GetMirroredX() + background_offset_.x(); 1359 1360 gfx::ImageSkia* tab_bg = GetThemeProvider()->GetImageSkiaNamed(tab_id); 1361 1362 TabImage* tab_image = &tab_active_; 1363 TabImage* tab_inactive_image = &tab_inactive_; 1364 TabImage* alpha = &tab_alpha_; 1365 1366 // If the theme is providing a custom background image, then its top edge 1367 // should be at the top of the tab. Otherwise, we assume that the background 1368 // image is a composited foreground + frame image. 1369 int bg_offset_y = GetThemeProvider()->HasCustomImage(tab_id) ? 1370 0 : background_offset_.y(); 1371 1372 // We need a gfx::Canvas object to be able to extract the image from. 1373 // We draw everything to this canvas and then output it to the canvas 1374 // parameter in addition to using it to mask the hover glow if needed. 1375 gfx::Canvas background_canvas(size(), canvas->image_scale(), false); 1376 1377 // Draw left edge. Don't draw over the toolbar, as we're not the foreground 1378 // tab. 1379 gfx::ImageSkia tab_l = gfx::ImageSkiaOperations::CreateTiledImage( 1380 *tab_bg, offset, bg_offset_y, tab_image->l_width, height()); 1381 gfx::ImageSkia theme_l = 1382 gfx::ImageSkiaOperations::CreateMaskedImage(tab_l, *alpha->image_l); 1383 background_canvas.DrawImageInt(theme_l, 1384 0, 0, theme_l.width(), theme_l.height() - kToolbarOverlap, 1385 0, 0, theme_l.width(), theme_l.height() - kToolbarOverlap, 1386 false); 1387 1388 // Draw right edge. Again, don't draw over the toolbar. 1389 gfx::ImageSkia tab_r = gfx::ImageSkiaOperations::CreateTiledImage(*tab_bg, 1390 offset + width() - tab_image->r_width, bg_offset_y, 1391 tab_image->r_width, height()); 1392 gfx::ImageSkia theme_r = 1393 gfx::ImageSkiaOperations::CreateMaskedImage(tab_r, *alpha->image_r); 1394 background_canvas.DrawImageInt(theme_r, 1395 0, 0, theme_r.width(), theme_r.height() - kToolbarOverlap, 1396 width() - theme_r.width(), 0, theme_r.width(), 1397 theme_r.height() - kToolbarOverlap, false); 1398 1399 // Draw center. Instead of masking out the top portion we simply skip over 1400 // it by incrementing by GetDropShadowHeight(), since it's a simple 1401 // rectangle. And again, don't draw over the toolbar. 1402 background_canvas.TileImageInt(*tab_bg, 1403 offset + tab_image->l_width, 1404 bg_offset_y + drop_shadow_height(), 1405 tab_image->l_width, 1406 drop_shadow_height(), 1407 width() - tab_image->l_width - tab_image->r_width, 1408 height() - drop_shadow_height() - kToolbarOverlap); 1409 1410 canvas->DrawImageInt( 1411 gfx::ImageSkia(background_canvas.ExtractImageRep()), 0, 0); 1412 1413 if (!GetThemeProvider()->HasCustomImage(tab_id) && 1414 hover_controller_.ShouldDraw()) { 1415 hover_controller_.Draw(canvas, gfx::ImageSkia( 1416 background_canvas.ExtractImageRep())); 1417 } 1418 1419 // Now draw the highlights/shadows around the tab edge. 1420 canvas->DrawImageInt(*tab_inactive_image->image_l, 0, 0); 1421 canvas->TileImageInt(*tab_inactive_image->image_c, 1422 tab_inactive_image->l_width, 0, 1423 width() - tab_inactive_image->l_width - 1424 tab_inactive_image->r_width, 1425 height()); 1426 canvas->DrawImageInt(*tab_inactive_image->image_r, 1427 width() - tab_inactive_image->r_width, 0); 1428 } 1429 1430 void Tab::PaintActiveTabBackground(gfx::Canvas* canvas) { 1431 gfx::ImageSkia* tab_background = 1432 GetThemeProvider()->GetImageSkiaNamed(IDR_THEME_TOOLBAR); 1433 int offset = GetMirroredX() + background_offset_.x(); 1434 1435 TabImage* tab_image = &tab_active_; 1436 TabImage* alpha = &tab_alpha_; 1437 1438 // Draw left edge. 1439 gfx::ImageSkia tab_l = gfx::ImageSkiaOperations::CreateTiledImage( 1440 *tab_background, offset, 0, tab_image->l_width, height()); 1441 gfx::ImageSkia theme_l = 1442 gfx::ImageSkiaOperations::CreateMaskedImage(tab_l, *alpha->image_l); 1443 canvas->DrawImageInt(theme_l, 0, 0); 1444 1445 // Draw right edge. 1446 gfx::ImageSkia tab_r = gfx::ImageSkiaOperations::CreateTiledImage( 1447 *tab_background, 1448 offset + width() - tab_image->r_width, 0, tab_image->r_width, height()); 1449 gfx::ImageSkia theme_r = 1450 gfx::ImageSkiaOperations::CreateMaskedImage(tab_r, *alpha->image_r); 1451 canvas->DrawImageInt(theme_r, width() - tab_image->r_width, 0); 1452 1453 // Draw center. Instead of masking out the top portion we simply skip over it 1454 // by incrementing by GetDropShadowHeight(), since it's a simple rectangle. 1455 canvas->TileImageInt(*tab_background, 1456 offset + tab_image->l_width, 1457 drop_shadow_height(), 1458 tab_image->l_width, 1459 drop_shadow_height(), 1460 width() - tab_image->l_width - tab_image->r_width, 1461 height() - drop_shadow_height()); 1462 1463 // Now draw the highlights/shadows around the tab edge. 1464 canvas->DrawImageInt(*tab_image->image_l, 0, 0); 1465 canvas->TileImageInt(*tab_image->image_c, tab_image->l_width, 0, 1466 width() - tab_image->l_width - tab_image->r_width, height()); 1467 canvas->DrawImageInt(*tab_image->image_r, width() - tab_image->r_width, 0); 1468 } 1469 1470 void Tab::PaintIcon(gfx::Canvas* canvas) { 1471 gfx::Rect bounds = GetIconBounds(); 1472 if (bounds.IsEmpty()) 1473 return; 1474 1475 bounds.set_x(GetMirroredXForRect(bounds)); 1476 1477 if (data().network_state != TabRendererData::NETWORK_STATE_NONE) { 1478 // Paint network activity (aka throbber) animation frame. 1479 ui::ThemeProvider* tp = GetThemeProvider(); 1480 gfx::ImageSkia frames(*tp->GetImageSkiaNamed( 1481 (data().network_state == TabRendererData::NETWORK_STATE_WAITING) ? 1482 IDR_THROBBER_WAITING : IDR_THROBBER)); 1483 1484 int icon_size = frames.height(); 1485 int image_offset = loading_animation_frame_ * icon_size; 1486 DrawIconCenter(canvas, frames, image_offset, 1487 icon_size, icon_size, 1488 bounds, false, SkPaint()); 1489 } else if (should_display_crashed_favicon_) { 1490 // Paint crash favicon. 1491 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 1492 gfx::ImageSkia crashed_favicon(*rb.GetImageSkiaNamed(IDR_SAD_FAVICON)); 1493 bounds.set_y(bounds.y() + favicon_hiding_offset_); 1494 DrawIconCenter(canvas, crashed_favicon, 0, 1495 crashed_favicon.width(), 1496 crashed_favicon.height(), 1497 bounds, true, SkPaint()); 1498 } else if (!data().favicon.isNull()) { 1499 // Paint the normal favicon. 1500 DrawIconCenter(canvas, data().favicon, 0, 1501 data().favicon.width(), 1502 data().favicon.height(), 1503 bounds, true, SkPaint()); 1504 } 1505 } 1506 1507 void Tab::PaintMediaIndicator(gfx::Canvas* canvas) { 1508 if (media_indicator_bounds_.IsEmpty() || !media_indicator_animation_) 1509 return; 1510 1511 gfx::Rect bounds = media_indicator_bounds_; 1512 bounds.set_x(GetMirroredXForRect(bounds)); 1513 1514 SkPaint paint; 1515 paint.setAntiAlias(true); 1516 double opaqueness = media_indicator_animation_->GetCurrentValue(); 1517 if (data_.media_state == TAB_MEDIA_STATE_NONE) 1518 opaqueness = 1.0 - opaqueness; // Fading out, not in. 1519 paint.setAlpha(opaqueness * SK_AlphaOPAQUE); 1520 1521 const gfx::ImageSkia& media_indicator_image = 1522 *(chrome::GetTabMediaIndicatorImage(animating_media_state_). 1523 ToImageSkia()); 1524 DrawIconAtLocation(canvas, media_indicator_image, 0, 1525 bounds.x(), bounds.y(), media_indicator_image.width(), 1526 media_indicator_image.height(), true, paint); 1527 } 1528 1529 void Tab::PaintTitle(gfx::Canvas* canvas, SkColor title_color) { 1530 // Paint the Title. 1531 base::string16 title = data().title; 1532 if (title.empty()) { 1533 title = data().loading ? 1534 l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) : 1535 CoreTabHelper::GetDefaultTitle(); 1536 } else { 1537 Browser::FormatTitleForDisplay(&title); 1538 } 1539 1540 canvas->DrawFadeTruncatingStringRect(title, gfx::Canvas::TruncateFadeTail, 1541 gfx::FontList(*font_), title_color, GetTitleBounds()); 1542 } 1543 1544 void Tab::AdvanceLoadingAnimation(TabRendererData::NetworkState old_state, 1545 TabRendererData::NetworkState state) { 1546 static bool initialized = false; 1547 static int loading_animation_frame_count = 0; 1548 static int waiting_animation_frame_count = 0; 1549 static int waiting_to_loading_frame_count_ratio = 0; 1550 if (!initialized) { 1551 initialized = true; 1552 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 1553 gfx::ImageSkia loading_animation(*rb.GetImageSkiaNamed(IDR_THROBBER)); 1554 loading_animation_frame_count = 1555 loading_animation.width() / loading_animation.height(); 1556 gfx::ImageSkia waiting_animation(*rb.GetImageSkiaNamed( 1557 IDR_THROBBER_WAITING)); 1558 waiting_animation_frame_count = 1559 waiting_animation.width() / waiting_animation.height(); 1560 waiting_to_loading_frame_count_ratio = 1561 waiting_animation_frame_count / loading_animation_frame_count; 1562 1563 base::debug::Alias(&loading_animation_frame_count); 1564 base::debug::Alias(&waiting_animation_frame_count); 1565 CHECK_NE(0, waiting_to_loading_frame_count_ratio) << 1566 "Number of frames in IDR_THROBBER must be equal to or greater " << 1567 "than the number of frames in IDR_THROBBER_WAITING. Please " << 1568 "investigate how this happened and update http://crbug.com/132590, " << 1569 "this is causing crashes in the wild."; 1570 } 1571 1572 // The waiting animation is the reverse of the loading animation, but at a 1573 // different rate - the following reverses and scales the animation_frame_ 1574 // so that the frame is at an equivalent position when going from one 1575 // animation to the other. 1576 if (state != old_state) { 1577 loading_animation_frame_ = loading_animation_frame_count - 1578 (loading_animation_frame_ / waiting_to_loading_frame_count_ratio); 1579 } 1580 1581 if (state == TabRendererData::NETWORK_STATE_WAITING) { 1582 loading_animation_frame_ = (loading_animation_frame_ + 1) % 1583 waiting_animation_frame_count; 1584 // Waiting steps backwards. 1585 immersive_loading_step_ = 1586 (immersive_loading_step_ - 1 + kImmersiveLoadingStepCount) % 1587 kImmersiveLoadingStepCount; 1588 } else if (state == TabRendererData::NETWORK_STATE_LOADING) { 1589 loading_animation_frame_ = (loading_animation_frame_ + 1) % 1590 loading_animation_frame_count; 1591 immersive_loading_step_ = (immersive_loading_step_ + 1) % 1592 kImmersiveLoadingStepCount; 1593 } else { 1594 loading_animation_frame_ = 0; 1595 immersive_loading_step_ = 0; 1596 } 1597 if (controller() && controller()->IsImmersiveStyle()) 1598 SchedulePaintInRect(GetImmersiveBarRect()); 1599 else 1600 ScheduleIconPaint(); 1601 } 1602 1603 int Tab::IconCapacity() const { 1604 if (height() < GetMinimumUnselectedSize().height()) 1605 return 0; 1606 const int available_width = 1607 std::max(0, width() - left_padding() - right_padding()); 1608 const int width_per_icon = tab_icon_size(); 1609 const int kPaddingBetweenIcons = 2; 1610 if (available_width >= width_per_icon && 1611 available_width < (width_per_icon + kPaddingBetweenIcons)) { 1612 return 1; 1613 } 1614 return available_width / (width_per_icon + kPaddingBetweenIcons); 1615 } 1616 1617 bool Tab::ShouldShowIcon() const { 1618 return chrome::ShouldTabShowFavicon( 1619 IconCapacity(), data().mini, IsActive(), data().show_icon, 1620 animating_media_state_); 1621 } 1622 1623 bool Tab::ShouldShowMediaIndicator() const { 1624 return chrome::ShouldTabShowMediaIndicator( 1625 IconCapacity(), data().mini, IsActive(), data().show_icon, 1626 animating_media_state_); 1627 } 1628 1629 bool Tab::ShouldShowCloseBox() const { 1630 return chrome::ShouldTabShowCloseButton( 1631 IconCapacity(), data().mini, IsActive()); 1632 } 1633 1634 double Tab::GetThrobValue() { 1635 bool is_selected = IsSelected(); 1636 double min = is_selected ? kSelectedTabOpacity : 0; 1637 double scale = is_selected ? kSelectedTabThrobScale : 1; 1638 1639 if (!data().mini) { 1640 if (tab_animation_.get() && tab_animation_->is_animating()) 1641 return tab_animation_->GetCurrentValue() * kHoverOpacity * scale + min; 1642 } 1643 1644 if (hover_controller_.ShouldDraw()) { 1645 return kHoverOpacity * hover_controller_.GetAnimationValue() * scale + 1646 min; 1647 } 1648 1649 return is_selected ? kSelectedTabOpacity : 0; 1650 } 1651 1652 void Tab::SetFaviconHidingOffset(int offset) { 1653 favicon_hiding_offset_ = offset; 1654 ScheduleIconPaint(); 1655 } 1656 1657 void Tab::DisplayCrashedFavicon() { 1658 should_display_crashed_favicon_ = true; 1659 } 1660 1661 void Tab::ResetCrashedFavicon() { 1662 should_display_crashed_favicon_ = false; 1663 } 1664 1665 void Tab::StopCrashAnimation() { 1666 crash_icon_animation_.reset(); 1667 } 1668 1669 void Tab::StartCrashAnimation() { 1670 crash_icon_animation_.reset(new FaviconCrashAnimation(this)); 1671 crash_icon_animation_->Start(); 1672 } 1673 1674 bool Tab::IsPerformingCrashAnimation() const { 1675 return crash_icon_animation_.get() && data_.IsCrashed(); 1676 } 1677 1678 void Tab::StartMediaIndicatorAnimation() { 1679 media_indicator_animation_ = 1680 chrome::CreateTabMediaIndicatorFadeAnimation(data_.media_state); 1681 media_indicator_animation_->set_delegate(this); 1682 media_indicator_animation_->Start(); 1683 } 1684 1685 void Tab::ScheduleIconPaint() { 1686 gfx::Rect bounds = GetIconBounds(); 1687 if (bounds.IsEmpty()) 1688 return; 1689 1690 // Extends the area to the bottom when sad_favicon is 1691 // animating. 1692 if (IsPerformingCrashAnimation()) 1693 bounds.set_height(height() - bounds.y()); 1694 bounds.set_x(GetMirroredXForRect(bounds)); 1695 SchedulePaintInRect(bounds); 1696 } 1697 1698 gfx::Rect Tab::GetImmersiveBarRect() const { 1699 // The main bar is as wide as the normal tab's horizontal top line. 1700 // This top line of the tab extends a few pixels left and right of the 1701 // center image due to pixels in the rounded corner images. 1702 const int kBarPadding = 1; 1703 int main_bar_left = tab_active_.l_width - kBarPadding; 1704 int main_bar_right = width() - tab_active_.r_width + kBarPadding; 1705 return gfx::Rect( 1706 main_bar_left, 0, main_bar_right - main_bar_left, kImmersiveBarHeight); 1707 } 1708 1709 void Tab::GetTabIdAndFrameId(views::Widget* widget, 1710 int* tab_id, 1711 int* frame_id) const { 1712 if (widget && widget->GetTopLevelWidget()->ShouldUseNativeFrame()) { 1713 *tab_id = IDR_THEME_TAB_BACKGROUND_V; 1714 *frame_id = 0; 1715 } else if (data().incognito) { 1716 *tab_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO; 1717 *frame_id = IDR_THEME_FRAME_INCOGNITO; 1718 #if defined(OS_WIN) 1719 } else if (win8::IsSingleWindowMetroMode()) { 1720 *tab_id = IDR_THEME_TAB_BACKGROUND_V; 1721 *frame_id = 0; 1722 #endif 1723 } else { 1724 *tab_id = IDR_THEME_TAB_BACKGROUND; 1725 *frame_id = IDR_THEME_FRAME; 1726 } 1727 } 1728 1729 //////////////////////////////////////////////////////////////////////////////// 1730 // Tab, private static: 1731 1732 // static 1733 void Tab::InitTabResources() { 1734 static bool initialized = false; 1735 if (initialized) 1736 return; 1737 1738 initialized = true; 1739 1740 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 1741 font_ = new gfx::Font(rb.GetFont(ui::ResourceBundle::BaseFont)); 1742 font_height_ = font_->GetHeight(); 1743 1744 image_cache_ = new ImageCache(); 1745 1746 // Load the tab images once now, and maybe again later if the theme changes. 1747 LoadTabImages(); 1748 } 1749 1750 // static 1751 void Tab::LoadTabImages() { 1752 // We're not letting people override tab images just yet. 1753 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 1754 1755 tab_alpha_.image_l = rb.GetImageSkiaNamed(IDR_TAB_ALPHA_LEFT); 1756 tab_alpha_.image_r = rb.GetImageSkiaNamed(IDR_TAB_ALPHA_RIGHT); 1757 1758 tab_active_.image_l = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_LEFT); 1759 tab_active_.image_c = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_CENTER); 1760 tab_active_.image_r = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_RIGHT); 1761 tab_active_.l_width = tab_active_.image_l->width(); 1762 tab_active_.r_width = tab_active_.image_r->width(); 1763 1764 tab_inactive_.image_l = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_LEFT); 1765 tab_inactive_.image_c = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_CENTER); 1766 tab_inactive_.image_r = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_RIGHT); 1767 tab_inactive_.l_width = tab_inactive_.image_l->width(); 1768 tab_inactive_.r_width = tab_inactive_.image_r->width(); 1769 } 1770 1771 // static 1772 gfx::ImageSkia Tab::GetCachedImage(int resource_id, 1773 const gfx::Size& size, 1774 ui::ScaleFactor scale_factor) { 1775 for (ImageCache::const_iterator i = image_cache_->begin(); 1776 i != image_cache_->end(); ++i) { 1777 if (i->resource_id == resource_id && i->scale_factor == scale_factor && 1778 i->image.size() == size) { 1779 return i->image; 1780 } 1781 } 1782 return gfx::ImageSkia(); 1783 } 1784 1785 // static 1786 void Tab::SetCachedImage(int resource_id, 1787 ui::ScaleFactor scale_factor, 1788 const gfx::ImageSkia& image) { 1789 DCHECK_NE(scale_factor, ui::SCALE_FACTOR_NONE); 1790 ImageCacheEntry entry; 1791 entry.resource_id = resource_id; 1792 entry.scale_factor = scale_factor; 1793 entry.image = image; 1794 image_cache_->push_front(entry); 1795 if (image_cache_->size() > kMaxImageCacheSize) 1796 image_cache_->pop_back(); 1797 } 1798