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