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