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