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