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