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