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