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.
5 #include "chrome/browser/ui/views/tabs/tab.h"
9 #include "base/command_line.h"
10 #include "base/debug/alias.h"
11 #include "base/profiler/scoped_tracker.h"
12 #include "base/strings/utf_string_conversions.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/media_indicator_button.h"
20 #include "chrome/browser/ui/views/tabs/tab_controller.h"
21 #include "chrome/browser/ui/views/theme_image_mapper.h"
22 #include "chrome/browser/ui/views/touch_uma/touch_uma.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/grit/generated_resources.h"
25 #include "content/public/browser/user_metrics.h"
26 #include "grit/theme_resources.h"
27 #include "third_party/skia/include/effects/SkGradientShader.h"
28 #include "ui/accessibility/ax_view_state.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/geometry/rect_conversions.h"
40 #include "ui/gfx/image/image_skia_operations.h"
41 #include "ui/gfx/path.h"
42 #include "ui/gfx/skia_util.h"
43 #include "ui/resources/grit/ui_resources.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/view_targeter.h"
49 #include "ui/views/widget/tooltip_manager.h"
50 #include "ui/views/widget/widget.h"
51 #include "ui/views/window/non_client_view.h"
54 #include "ui/aura/env.h"
57 using base::UserMetricsAction
;
61 // Padding around the "content" of a tab, occupied by the tab border graphics.
62 const int kLeftPadding
= 22;
63 const int kTopPadding
= 4;
64 const int kRightPadding
= 17;
65 const int kBottomPadding
= 2;
67 // Height of the shadow at the top of the tab image assets.
68 const int kDropShadowHeight
= 4;
70 // How long the pulse throb takes.
71 const int kPulseDurationMs
= 200;
73 // Width of touch tabs.
74 static const int kTouchWidth
= 120;
76 static const int kToolbarOverlap
= 1;
77 static const int kFaviconTitleSpacing
= 4;
78 static const int kViewSpacing
= 3;
79 static const int kStandardTitleWidth
= 175;
81 // Width of mini-tabs.
82 const int kMiniTabWidth
= 64;
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.
88 static const int kMiniTabRendererAsNormalTabWidth
= kMiniTabWidth
+ 30;
90 // How opaque to make the hover state (out of 1).
91 static const double kHoverOpacity
= 0.33;
93 // Opacity for non-active selected tabs.
94 static const double kSelectedTabOpacity
= .45;
96 // Selected (but not active) tabs have their throb value scaled down by this.
97 static const double kSelectedTabThrobScale
= .5;
99 // Durations for the various parts of the mini tab title animation.
100 static const int kMiniTitleChangeAnimationDuration1MS
= 1600;
101 static const int kMiniTitleChangeAnimationStart1MS
= 0;
102 static const int kMiniTitleChangeAnimationEnd1MS
= 1900;
103 static const int kMiniTitleChangeAnimationDuration2MS
= 0;
104 static const int kMiniTitleChangeAnimationDuration3MS
= 550;
105 static const int kMiniTitleChangeAnimationStart3MS
= 150;
106 static const int kMiniTitleChangeAnimationEnd3MS
= 800;
107 static const int kMiniTitleChangeAnimationIntervalMS
= 40;
109 // Offset from the right edge for the start of the mini title change animation.
110 static const int kMiniTitleChangeInitialXOffset
= 6;
112 // Radius of the radial gradient used for mini title change animation.
113 static const int kMiniTitleChangeGradientRadius
= 20;
115 // Colors of the gradient used during the mini title change animation.
116 static const SkColor kMiniTitleChangeGradientColor1
= SK_ColorWHITE
;
117 static const SkColor kMiniTitleChangeGradientColor2
=
118 SkColorSetARGB(0, 255, 255, 255);
120 // Max number of images to cache. This has to be at least two since rounding
121 // errors may lead to tabs in the same tabstrip having different sizes.
122 const size_t kMaxImageCacheSize
= 4;
124 // Height of the miniature tab strip in immersive mode.
125 const int kImmersiveTabHeight
= 3;
127 // Height of the small tab indicator rectangles in immersive mode.
128 const int kImmersiveBarHeight
= 2;
130 // Color for active and inactive tabs in the immersive mode light strip. These
131 // should be a little brighter than the color of the normal art assets for tabs,
132 // which for active tabs is 230, 230, 230 and for inactive is 184, 184, 184.
133 const SkColor kImmersiveActiveTabColor
= SkColorSetRGB(235, 235, 235);
134 const SkColor kImmersiveInactiveTabColor
= SkColorSetRGB(190, 190, 190);
136 // The minimum opacity (out of 1) when a tab (either active or inactive) is
137 // throbbing in the immersive mode light strip.
138 const double kImmersiveTabMinThrobOpacity
= 0.66;
140 // Number of steps in the immersive mode loading animation.
141 const int kImmersiveLoadingStepCount
= 32;
143 const char kTabCloseButtonName
[] = "TabCloseButton";
145 void DrawIconAtLocation(gfx::Canvas
* canvas
,
146 const gfx::ImageSkia
& image
,
153 const SkPaint
& paint
) {
154 // NOTE: the clipping is a work around for 69528, it shouldn't be necessary.
156 canvas
->ClipRect(gfx::Rect(dst_x
, dst_y
, icon_width
, icon_height
));
157 canvas
->DrawImageInt(image
,
158 image_offset
, 0, icon_width
, icon_height
,
159 dst_x
, dst_y
, icon_width
, icon_height
,
164 // Draws the icon image at the center of |bounds|.
165 void DrawIconCenter(gfx::Canvas
* canvas
,
166 const gfx::ImageSkia
& image
,
170 const gfx::Rect
& bounds
,
172 const SkPaint
& paint
) {
173 // Center the image within bounds.
174 int dst_x
= bounds
.x() - (icon_width
- bounds
.width()) / 2;
175 int dst_y
= bounds
.y() - (icon_height
- bounds
.height()) / 2;
176 DrawIconAtLocation(canvas
, image
, image_offset
, dst_x
, dst_y
, icon_width
,
177 icon_height
, filter
, paint
);
180 chrome::HostDesktopType
GetHostDesktopType(views::View
* view
) {
181 // Widget is NULL when tabs are detached.
182 views::Widget
* widget
= view
->GetWidget();
183 return chrome::GetHostDesktopTypeForNativeView(
184 widget
? widget
->GetNativeView() : NULL
);
187 // Stop()s |animation| and then deletes it. We do this rather than just deleting
188 // so that the delegate is notified before the destruction.
189 void StopAndDeleteAnimation(scoped_ptr
<gfx::Animation
> animation
) {
196 ////////////////////////////////////////////////////////////////////////////////
197 // FaviconCrashAnimation
199 // A custom animation subclass to manage the favicon crash animation.
200 class Tab::FaviconCrashAnimation
: public gfx::LinearAnimation
,
201 public gfx::AnimationDelegate
{
203 explicit FaviconCrashAnimation(Tab
* target
)
204 : gfx::LinearAnimation(1000, 25, this),
207 ~FaviconCrashAnimation() override
{}
209 // gfx::Animation overrides:
210 void AnimateToState(double state
) override
{
211 const double kHidingOffset
= 27;
214 target_
->SetFaviconHidingOffset(
215 static_cast<int>(floor(kHidingOffset
* 2.0 * state
)));
217 target_
->DisplayCrashedFavicon();
218 target_
->SetFaviconHidingOffset(
220 floor(kHidingOffset
- ((state
- .5) * 2.0 * kHidingOffset
))));
224 // gfx::AnimationDelegate overrides:
225 void AnimationCanceled(const gfx::Animation
* animation
) override
{
226 target_
->SetFaviconHidingOffset(0);
232 DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation
);
235 ////////////////////////////////////////////////////////////////////////////////
238 // This is a Button subclass that causes middle clicks to be forwarded to the
239 // parent View by explicitly not handling them in OnMousePressed.
240 class Tab::TabCloseButton
: public views::ImageButton
,
241 public views::MaskedTargeterDelegate
{
243 explicit TabCloseButton(Tab
* tab
)
244 : views::ImageButton(tab
),
247 scoped_ptr
<views::ViewTargeter
>(new views::ViewTargeter(this)));
250 ~TabCloseButton() override
{}
253 View
* GetTooltipHandlerForPoint(const gfx::Point
& point
) override
{
254 // Tab close button has no children, so tooltip handler should be the same
255 // as the event handler.
256 // In addition, a hit test has to be performed for the point (as
257 // GetTooltipHandlerForPoint() is responsible for it).
258 if (!HitTestPoint(point
))
260 return GetEventHandlerForPoint(point
);
263 bool OnMousePressed(const ui::MouseEvent
& event
) override
{
264 tab_
->controller_
->OnMouseEventInTab(this, event
);
266 bool handled
= ImageButton::OnMousePressed(event
);
267 // Explicitly mark midle-mouse clicks as non-handled to ensure the tab
269 return event
.IsOnlyMiddleMouseButton() ? false : handled
;
272 void OnMouseMoved(const ui::MouseEvent
& event
) override
{
273 tab_
->controller_
->OnMouseEventInTab(this, event
);
274 CustomButton::OnMouseMoved(event
);
277 void OnMouseReleased(const ui::MouseEvent
& event
) override
{
278 tab_
->controller_
->OnMouseEventInTab(this, event
);
279 CustomButton::OnMouseReleased(event
);
282 void OnGestureEvent(ui::GestureEvent
* event
) override
{
283 // Consume all gesture events here so that the parent (Tab) does not
284 // start consuming gestures.
285 ImageButton::OnGestureEvent(event
);
289 const char* GetClassName() const override
{ return kTabCloseButtonName
; }
292 // Returns the rectangular bounds of parent tab's visible region in the
293 // local coordinate space of |this|.
294 gfx::Rect
GetTabBounds() const {
296 tab_
->GetHitTestMask(&tab_mask
);
298 gfx::RectF
tab_bounds_f(gfx::SkRectToRectF(tab_mask
.getBounds()));
299 views::View::ConvertRectToTarget(tab_
, this, &tab_bounds_f
);
300 return gfx::ToEnclosingRect(tab_bounds_f
);
303 // Returns the rectangular bounds of the tab close button in the local
304 // coordinate space of |this|, not including clipped regions on the top
305 // or bottom of the button. |tab_bounds| is the rectangular bounds of
306 // the parent tab's visible region in the local coordinate space of |this|.
307 gfx::Rect
GetTabCloseButtonBounds(const gfx::Rect
& tab_bounds
) const {
308 gfx::Rect
button_bounds(GetContentsBounds());
309 button_bounds
.set_x(GetMirroredXForRect(button_bounds
));
311 int top_overflow
= tab_bounds
.y() - button_bounds
.y();
312 int bottom_overflow
= button_bounds
.bottom() - tab_bounds
.bottom();
313 if (top_overflow
> 0)
314 button_bounds
.set_y(tab_bounds
.y());
315 else if (bottom_overflow
> 0)
316 button_bounds
.set_height(button_bounds
.height() - bottom_overflow
);
318 return button_bounds
;
321 // views::ViewTargeterDelegate:
322 View
* TargetForRect(View
* root
, const gfx::Rect
& rect
) override
{
323 CHECK_EQ(root
, this);
325 if (!views::UsePointBasedTargeting(rect
))
326 return ViewTargeterDelegate::TargetForRect(root
, rect
);
328 // Ignore the padding set on the button.
329 gfx::Rect contents_bounds
= GetContentsBounds();
330 contents_bounds
.set_x(GetMirroredXForRect(contents_bounds
));
332 #if defined(USE_AURA)
333 // Include the padding in hit-test for touch events.
334 if (aura::Env::GetInstance()->is_touch_down())
335 contents_bounds
= GetLocalBounds();
338 return contents_bounds
.Intersects(rect
) ? this : parent();
341 // views:MaskedTargeterDelegate:
342 bool GetHitTestMask(gfx::Path
* mask
) const override
{
346 // The parent tab may be partially occluded by another tab if we are
347 // in stacked tab mode, which means that the tab close button may also
348 // be partially occluded. Define the hit test mask of the tab close
349 // button to be the intersection of the parent tab's visible bounds
350 // and the bounds of the tab close button.
351 gfx::Rect
tab_bounds(GetTabBounds());
352 gfx::Rect
button_bounds(GetTabCloseButtonBounds(tab_bounds
));
353 gfx::Rect
intersection(gfx::IntersectRects(tab_bounds
, button_bounds
));
355 if (!intersection
.IsEmpty()) {
356 mask
->addRect(RectToSkRect(intersection
));
363 bool DoesIntersectRect(const View
* target
,
364 const gfx::Rect
& rect
) const override
{
365 CHECK_EQ(target
, this);
367 // If the request is not made in response to a gesture, use the
368 // default implementation.
369 if (views::UsePointBasedTargeting(rect
))
370 return MaskedTargeterDelegate::DoesIntersectRect(target
, rect
);
372 // The hit test request is in response to a gesture. Return false if any
373 // part of the tab close button is hidden from the user.
374 // TODO(tdanderson): Consider always returning the intersection if the
375 // non-rectangular shape of the tab can be accounted for.
376 gfx::Rect
tab_bounds(GetTabBounds());
377 gfx::Rect
button_bounds(GetTabCloseButtonBounds(tab_bounds
));
378 if (!tab_bounds
.Contains(button_bounds
))
381 return MaskedTargeterDelegate::DoesIntersectRect(target
, rect
);
386 DISALLOW_COPY_AND_ASSIGN(TabCloseButton
);
389 ////////////////////////////////////////////////////////////////////////////////
392 Tab::ImageCacheEntry::ImageCacheEntry()
394 scale_factor(ui::SCALE_FACTOR_NONE
) {
397 Tab::ImageCacheEntry::~ImageCacheEntry() {}
399 ////////////////////////////////////////////////////////////////////////////////
403 const char Tab::kViewClassName
[] = "Tab";
404 Tab::TabImage
Tab::tab_active_
= {0};
405 Tab::TabImage
Tab::tab_inactive_
= {0};
406 Tab::TabImage
Tab::tab_alpha_
= {0};
407 Tab::ImageCache
* Tab::image_cache_
= NULL
;
409 ////////////////////////////////////////////////////////////////////////////////
412 Tab::Tab(TabController
* controller
)
413 : controller_(controller
),
417 favicon_hiding_offset_(0),
418 loading_animation_frame_(0),
419 immersive_loading_step_(0),
420 should_display_crashed_favicon_(false),
422 media_indicator_button_(NULL
),
423 title_(new views::Label()),
424 tab_activated_with_last_tap_down_(false),
425 hover_controller_(this),
426 showing_icon_(false),
427 showing_media_indicator_(false),
428 showing_close_button_(false),
429 close_button_color_(0) {
433 // So we get don't get enter/exit on children and don't prematurely stop the
435 set_notify_enter_exit_on_child(true);
439 title_
->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD
);
440 title_
->SetElideBehavior(gfx::FADE_TAIL
);
441 title_
->SetHandlesTooltips(false);
442 title_
->SetAutoColorReadabilityEnabled(false);
443 title_
->SetText(CoreTabHelper::GetDefaultTitle());
444 AddChildView(title_
);
447 scoped_ptr
<views::ViewTargeter
>(new views::ViewTargeter(this)));
449 // Add the Close Button.
450 close_button_
= new TabCloseButton(this);
451 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
452 close_button_
->SetImage(views::CustomButton::STATE_NORMAL
,
453 rb
.GetImageSkiaNamed(IDR_CLOSE_1
));
454 close_button_
->SetImage(views::CustomButton::STATE_HOVERED
,
455 rb
.GetImageSkiaNamed(IDR_CLOSE_1_H
));
456 close_button_
->SetImage(views::CustomButton::STATE_PRESSED
,
457 rb
.GetImageSkiaNamed(IDR_CLOSE_1_P
));
458 close_button_
->SetAccessibleName(
459 l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE
));
460 // Disable animation so that the red danger sign shows up immediately
461 // to help avoid mis-clicks.
462 close_button_
->SetAnimationDuration(0);
463 AddChildView(close_button_
);
465 set_context_menu_controller(this);
471 void Tab::set_animation_container(gfx::AnimationContainer
* container
) {
472 animation_container_
= container
;
473 hover_controller_
.SetAnimationContainer(container
);
476 bool Tab::IsActive() const {
477 return controller_
->IsActiveTab(this);
480 bool Tab::IsSelected() const {
481 return controller_
->IsTabSelected(this);
484 void Tab::SetData(const TabRendererData
& data
) {
485 if (data_
.Equals(data
))
488 TabRendererData
old(data_
);
491 base::string16 title
= data_
.title
;
493 title
= data_
.loading
?
494 l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE
) :
495 CoreTabHelper::GetDefaultTitle();
497 Browser::FormatTitleForDisplay(&title
);
499 title_
->SetText(title
);
501 if (data_
.IsCrashed()) {
502 if (!should_display_crashed_favicon_
&& !IsPerformingCrashAnimation()) {
503 data_
.media_state
= TAB_MEDIA_STATE_NONE
;
504 #if defined(OS_CHROMEOS)
505 // On Chrome OS, we reload killed tabs automatically when the user
506 // switches to them. Don't display animations for these unless they're
507 // selected (i.e. in the foreground) -- we won't reload these
508 // automatically since we don't want to get into a crash loop.
510 data_
.crashed_status
!= base::TERMINATION_STATUS_PROCESS_WAS_KILLED
)
511 StartCrashAnimation();
513 StartCrashAnimation();
517 if (IsPerformingCrashAnimation())
518 StopCrashAnimation();
519 ResetCrashedFavicon();
522 if (data_
.media_state
!= old
.media_state
)
523 GetMediaIndicatorButton()->TransitionToMediaState(data_
.media_state
);
525 if (old
.mini
!= data_
.mini
) {
526 StopAndDeleteAnimation(mini_title_change_animation_
.Pass());
535 void Tab::UpdateLoadingAnimation(TabRendererData::NetworkState state
) {
536 if (state
== data_
.network_state
&&
537 state
== TabRendererData::NETWORK_STATE_NONE
) {
538 // If the network state is none and hasn't changed, do nothing. Otherwise we
539 // need to advance the animation frame.
543 TabRendererData::NetworkState old_state
= data_
.network_state
;
544 data_
.network_state
= state
;
545 AdvanceLoadingAnimation(old_state
, state
);
548 void Tab::StartPulse() {
549 pulse_animation_
.reset(new gfx::ThrobAnimation(this));
550 pulse_animation_
->SetSlideDuration(kPulseDurationMs
);
551 if (animation_container_
.get())
552 pulse_animation_
->SetContainer(animation_container_
.get());
553 pulse_animation_
->StartThrobbing(std::numeric_limits
<int>::max());
556 void Tab::StopPulse() {
557 StopAndDeleteAnimation(pulse_animation_
.Pass());
560 void Tab::StartMiniTabTitleAnimation() {
563 if (!mini_title_change_animation_
) {
564 gfx::MultiAnimation::Parts parts
;
566 gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration1MS
,
567 gfx::Tween::EASE_OUT
));
569 gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration2MS
,
572 gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration3MS
,
573 gfx::Tween::EASE_IN
));
574 parts
[0].start_time_ms
= kMiniTitleChangeAnimationStart1MS
;
575 parts
[0].end_time_ms
= kMiniTitleChangeAnimationEnd1MS
;
576 parts
[2].start_time_ms
= kMiniTitleChangeAnimationStart3MS
;
577 parts
[2].end_time_ms
= kMiniTitleChangeAnimationEnd3MS
;
578 base::TimeDelta timeout
=
579 base::TimeDelta::FromMilliseconds(kMiniTitleChangeAnimationIntervalMS
);
580 mini_title_change_animation_
.reset(new gfx::MultiAnimation(parts
, timeout
));
581 if (animation_container_
.get())
582 mini_title_change_animation_
->SetContainer(animation_container_
.get());
583 mini_title_change_animation_
->set_delegate(this);
585 mini_title_change_animation_
->Start();
588 void Tab::StopMiniTabTitleAnimation() {
589 StopAndDeleteAnimation(mini_title_change_animation_
.Pass());
593 gfx::Size
Tab::GetBasicMinimumUnselectedSize() {
596 gfx::Size minimum_size
;
597 minimum_size
.set_width(kLeftPadding
+ kRightPadding
);
598 // Since we use image images, the real minimum height of the image is
599 // defined most accurately by the height of the end cap images.
600 minimum_size
.set_height(tab_active_
.image_l
->height());
604 gfx::Size
Tab::GetMinimumUnselectedSize() {
605 return GetBasicMinimumUnselectedSize();
609 gfx::Size
Tab::GetMinimumSelectedSize() {
610 gfx::Size minimum_size
= GetBasicMinimumUnselectedSize();
611 minimum_size
.set_width(
612 kLeftPadding
+ gfx::kFaviconSize
+ kRightPadding
);
617 gfx::Size
Tab::GetStandardSize() {
618 gfx::Size standard_size
= GetBasicMinimumUnselectedSize();
619 standard_size
.set_width(
620 standard_size
.width() + kFaviconTitleSpacing
+ kStandardTitleWidth
);
621 return standard_size
;
625 int Tab::GetTouchWidth() {
630 int Tab::GetMiniWidth() {
631 return kMiniTabWidth
;
635 int Tab::GetImmersiveHeight() {
636 return kImmersiveTabHeight
;
639 ////////////////////////////////////////////////////////////////////////////////
640 // Tab, AnimationDelegate overrides:
642 void Tab::AnimationProgressed(const gfx::Animation
* animation
) {
643 // Ignore if the pulse animation is being performed on active tab because
644 // it repaints the same image. See |Tab::PaintTabBackground()|.
645 if (animation
== pulse_animation_
.get() && IsActive())
650 void Tab::AnimationCanceled(const gfx::Animation
* animation
) {
654 void Tab::AnimationEnded(const gfx::Animation
* animation
) {
658 ////////////////////////////////////////////////////////////////////////////////
659 // Tab, views::ButtonListener overrides:
661 void Tab::ButtonPressed(views::Button
* sender
, const ui::Event
& event
) {
662 if (media_indicator_button_
&& media_indicator_button_
->visible()) {
663 if (media_indicator_button_
->enabled())
664 content::RecordAction(UserMetricsAction("CloseTab_MuteToggleAvailable"));
665 else if (data_
.media_state
== TAB_MEDIA_STATE_AUDIO_PLAYING
)
666 content::RecordAction(UserMetricsAction("CloseTab_AudioIndicator"));
668 content::RecordAction(UserMetricsAction("CloseTab_RecordingIndicator"));
670 content::RecordAction(UserMetricsAction("CloseTab_NoMediaIndicator"));
673 const CloseTabSource source
=
674 (event
.type() == ui::ET_MOUSE_RELEASED
&&
675 (event
.flags() & ui::EF_FROM_TOUCH
) == 0) ? CLOSE_TAB_FROM_MOUSE
:
676 CLOSE_TAB_FROM_TOUCH
;
677 DCHECK_EQ(close_button_
, sender
);
678 controller_
->CloseTab(this, source
);
679 if (event
.type() == ui::ET_GESTURE_TAP
)
680 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_TABCLOSE_TAP
);
683 ////////////////////////////////////////////////////////////////////////////////
684 // Tab, views::ContextMenuController overrides:
686 void Tab::ShowContextMenuForView(views::View
* source
,
687 const gfx::Point
& point
,
688 ui::MenuSourceType source_type
) {
690 controller_
->ShowContextMenuForTab(this, point
, source_type
);
693 ////////////////////////////////////////////////////////////////////////////////
694 // Tab, views::MaskedTargeterDelegate overrides:
696 bool Tab::GetHitTestMask(gfx::Path
* mask
) const {
699 // When the window is maximized we don't want to shave off the edges or top
700 // shadow of the tab, such that the user can click anywhere along the top
701 // edge of the screen to select a tab. Ditto for immersive fullscreen.
702 const views::Widget
* widget
= GetWidget();
703 bool include_top_shadow
=
704 widget
&& (widget
->IsMaximized() || widget
->IsFullscreen());
705 TabResources::GetHitTestMask(width(), height(), include_top_shadow
, mask
);
707 // It is possible for a portion of the tab to be occluded if tabs are
708 // stacked, so modify the hit test mask to only include the visible
709 // region of the tab.
711 controller_
->ShouldPaintTab(this, &clip
);
712 if (clip
.size().GetArea()) {
713 SkRect
intersection(mask
->getBounds());
715 if (!intersection
.intersect(RectToSkRect(clip
)))
717 mask
->addRect(intersection
);
722 ////////////////////////////////////////////////////////////////////////////////
723 // Tab, views::View overrides:
725 void Tab::OnPaint(gfx::Canvas
* canvas
) {
726 // Don't paint if we're narrower than we can render correctly. (This should
727 // only happen during animations).
728 if (width() < GetMinimumUnselectedSize().width() && !data().mini
)
732 if (!controller_
->ShouldPaintTab(this, &clip
))
734 if (!clip
.IsEmpty()) {
736 canvas
->ClipRect(clip
);
739 if (controller_
->IsImmersiveStyle())
740 PaintImmersiveTab(canvas
);
749 gfx::Rect lb
= GetContentsBounds();
753 lb
.Inset(kLeftPadding
, kTopPadding
, kRightPadding
, kBottomPadding
);
754 showing_icon_
= ShouldShowIcon();
755 favicon_bounds_
.SetRect(lb
.x(), lb
.y(), 0, 0);
757 favicon_bounds_
.set_size(gfx::Size(gfx::kFaviconSize
, gfx::kFaviconSize
));
758 favicon_bounds_
.set_y(lb
.y() + (lb
.height() - gfx::kFaviconSize
+ 1) / 2);
759 MaybeAdjustLeftForMiniTab(&favicon_bounds_
);
762 showing_close_button_
= ShouldShowCloseBox();
763 if (showing_close_button_
) {
764 // If the ratio of the close button size to tab width exceeds the maximum.
765 // The close button should be as large as possible so that there is a larger
766 // hit-target for touch events. So the close button bounds extends to the
767 // edges of the tab. However, the larger hit-target should be active only
768 // for mouse events, and the close-image should show up in the right place.
769 // So a border is added to the button with necessary padding. The close
770 // button (BaseTab::TabCloseButton) makes sure the padding is a hit-target
771 // only for touch events.
772 close_button_
->SetBorder(views::Border::NullBorder());
773 const gfx::Size
close_button_size(close_button_
->GetPreferredSize());
774 const int top
= lb
.y() + (lb
.height() - close_button_size
.height() + 1) / 2;
775 const int bottom
= height() - (close_button_size
.height() + top
);
776 const int left
= kViewSpacing
;
777 const int right
= width() - (lb
.width() + close_button_size
.width() + left
);
778 close_button_
->SetBorder(
779 views::Border::CreateEmptyBorder(top
, left
, bottom
, right
));
780 close_button_
->SetPosition(gfx::Point(lb
.width(), 0));
781 close_button_
->SizeToPreferredSize();
783 close_button_
->SetVisible(showing_close_button_
);
785 showing_media_indicator_
= ShouldShowMediaIndicator();
786 if (showing_media_indicator_
) {
787 views::ImageButton
* const button
= GetMediaIndicatorButton();
788 const gfx::Size
image_size(button
->GetPreferredSize());
789 const int right
= showing_close_button_
?
790 close_button_
->x() + close_button_
->GetInsets().left() : lb
.right();
792 std::max(lb
.x(), right
- image_size
.width()),
793 lb
.y() + (lb
.height() - image_size
.height() + 1) / 2,
795 image_size
.height());
796 MaybeAdjustLeftForMiniTab(&bounds
);
797 button
->SetBoundsRect(bounds
);
798 button
->SetVisible(true);
799 } else if (media_indicator_button_
) {
800 media_indicator_button_
->SetVisible(false);
803 // Size the title to fill the remaining width and use all available height.
804 bool show_title
= !data().mini
|| width() >= kMiniTabRendererAsNormalTabWidth
;
806 int title_left
= favicon_bounds_
.right() + kFaviconTitleSpacing
;
807 int title_width
= lb
.width() - title_left
;
808 if (showing_media_indicator_
) {
809 title_width
= media_indicator_button_
->x() - kViewSpacing
- title_left
;
810 } else if (close_button_
->visible()) {
811 // Allow the title to overlay the close button's empty border padding.
812 title_width
= close_button_
->x() + close_button_
->GetInsets().left() -
813 kViewSpacing
- title_left
;
815 gfx::Rect
rect(title_left
, lb
.y(), std::max(title_width
, 0), lb
.height());
816 const int title_height
= title_
->GetPreferredSize().height();
817 if (title_height
> rect
.height()) {
818 rect
.set_y(lb
.y() - (title_height
- rect
.height()) / 2);
819 rect
.set_height(title_height
);
821 title_
->SetBoundsRect(rect
);
823 title_
->SetVisible(show_title
);
826 void Tab::OnThemeChanged() {
830 const char* Tab::GetClassName() const {
831 return kViewClassName
;
834 bool Tab::GetTooltipText(const gfx::Point
& p
, base::string16
* tooltip
) const {
835 // Note: Anything that affects the tooltip text should be accounted for when
836 // calling TooltipTextChanged() from Tab::DataChanged().
837 *tooltip
= chrome::AssembleTabTooltipText(data_
.title
, data_
.media_state
);
838 return !tooltip
->empty();
841 bool Tab::GetTooltipTextOrigin(const gfx::Point
& p
, gfx::Point
* origin
) const {
842 origin
->set_x(title_
->x() + 10);
847 bool Tab::OnMousePressed(const ui::MouseEvent
& event
) {
848 controller_
->OnMouseEventInTab(this, event
);
850 // Allow a right click from touch to drag, which corresponds to a long click.
851 if (event
.IsOnlyLeftMouseButton() ||
852 (event
.IsOnlyRightMouseButton() && event
.flags() & ui::EF_FROM_TOUCH
)) {
853 ui::ListSelectionModel original_selection
;
854 original_selection
.Copy(controller_
->GetSelectionModel());
855 // Changing the selection may cause our bounds to change. If that happens
856 // the location of the event may no longer be valid. Create a copy of the
857 // event in the parents coordinate, which won't change, and recreate an
858 // event after changing so the coordinates are correct.
859 ui::MouseEvent
event_in_parent(event
, static_cast<View
*>(this), parent());
860 if (controller_
->SupportsMultipleSelection()) {
861 if (event
.IsShiftDown() && event
.IsControlDown()) {
862 controller_
->AddSelectionFromAnchorTo(this);
863 } else if (event
.IsShiftDown()) {
864 controller_
->ExtendSelectionTo(this);
865 } else if (event
.IsControlDown()) {
866 controller_
->ToggleSelected(this);
868 // Don't allow dragging non-selected tabs.
871 } else if (!IsSelected()) {
872 controller_
->SelectTab(this);
874 } else if (!IsSelected()) {
875 controller_
->SelectTab(this);
877 ui::MouseEvent
cloned_event(event_in_parent
, parent(),
878 static_cast<View
*>(this));
879 controller_
->MaybeStartDrag(this, cloned_event
, original_selection
);
884 bool Tab::OnMouseDragged(const ui::MouseEvent
& event
) {
885 controller_
->ContinueDrag(this, event
);
889 void Tab::OnMouseReleased(const ui::MouseEvent
& event
) {
890 controller_
->OnMouseEventInTab(this, event
);
892 // Notify the drag helper that we're done with any potential drag operations.
893 // Clean up the drag helper, which is re-created on the next mouse press.
894 // In some cases, ending the drag will schedule the tab for destruction; if
895 // so, bail immediately, since our members are already dead and we shouldn't
896 // do anything else except drop the tab where it is.
897 if (controller_
->EndDrag(END_DRAG_COMPLETE
))
900 // Close tab on middle click, but only if the button is released over the tab
901 // (normal windows behavior is to discard presses of a UI element where the
902 // releases happen off the element).
903 if (event
.IsMiddleMouseButton()) {
904 if (HitTestPoint(event
.location())) {
905 controller_
->CloseTab(this, CLOSE_TAB_FROM_MOUSE
);
906 } else if (closing_
) {
907 // We're animating closed and a middle mouse button was pushed on us but
908 // we don't contain the mouse anymore. We assume the user is clicking
909 // quicker than the animation and we should close the tab that falls under
911 Tab
* closest_tab
= controller_
->GetTabAt(this, event
.location());
913 controller_
->CloseTab(closest_tab
, CLOSE_TAB_FROM_MOUSE
);
915 } else if (event
.IsOnlyLeftMouseButton() && !event
.IsShiftDown() &&
916 !event
.IsControlDown()) {
917 // If the tab was already selected mouse pressed doesn't change the
918 // selection. Reset it now to handle the case where multiple tabs were
920 controller_
->SelectTab(this);
922 if (media_indicator_button_
&& media_indicator_button_
->visible() &&
923 media_indicator_button_
->bounds().Contains(event
.location())) {
924 content::RecordAction(UserMetricsAction("TabMediaIndicator_Clicked"));
929 void Tab::OnMouseCaptureLost() {
930 controller_
->EndDrag(END_DRAG_CAPTURE_LOST
);
933 void Tab::OnMouseEntered(const ui::MouseEvent
& event
) {
934 hover_controller_
.Show(views::GlowHoverController::SUBTLE
);
937 void Tab::OnMouseMoved(const ui::MouseEvent
& event
) {
938 hover_controller_
.SetLocation(event
.location());
939 controller_
->OnMouseEventInTab(this, event
);
942 void Tab::OnMouseExited(const ui::MouseEvent
& event
) {
943 hover_controller_
.Hide();
946 void Tab::OnGestureEvent(ui::GestureEvent
* event
) {
947 switch (event
->type()) {
948 case ui::ET_GESTURE_TAP_DOWN
: {
949 // TAP_DOWN is only dispatched for the first touch point.
950 DCHECK_EQ(1, event
->details().touch_points());
952 // See comment in OnMousePressed() as to why we copy the event.
953 ui::GestureEvent
event_in_parent(*event
, static_cast<View
*>(this),
955 ui::ListSelectionModel original_selection
;
956 original_selection
.Copy(controller_
->GetSelectionModel());
957 tab_activated_with_last_tap_down_
= !IsActive();
959 controller_
->SelectTab(this);
960 gfx::Point
loc(event
->location());
961 views::View::ConvertPointToScreen(this, &loc
);
962 ui::GestureEvent
cloned_event(event_in_parent
, parent(),
963 static_cast<View
*>(this));
964 controller_
->MaybeStartDrag(this, cloned_event
, original_selection
);
968 case ui::ET_GESTURE_END
:
969 controller_
->EndDrag(END_DRAG_COMPLETE
);
972 case ui::ET_GESTURE_SCROLL_UPDATE
:
973 controller_
->ContinueDrag(this, *event
);
982 void Tab::GetAccessibleState(ui::AXViewState
* state
) {
983 state
->role
= ui::AX_ROLE_TAB
;
984 state
->name
= data_
.title
;
985 state
->AddStateFlag(ui::AX_STATE_MULTISELECTABLE
);
986 state
->AddStateFlag(ui::AX_STATE_SELECTABLE
);
987 controller_
->UpdateTabAccessibilityState(this, state
);
989 state
->AddStateFlag(ui::AX_STATE_SELECTED
);
992 ////////////////////////////////////////////////////////////////////////////////
995 void Tab::MaybeAdjustLeftForMiniTab(gfx::Rect
* bounds
) const {
996 if (!data().mini
|| width() >= kMiniTabRendererAsNormalTabWidth
)
998 const int mini_delta
= kMiniTabRendererAsNormalTabWidth
- GetMiniWidth();
999 const int ideal_delta
= width() - GetMiniWidth();
1000 const int ideal_x
= (GetMiniWidth() - bounds
->width()) / 2;
1001 bounds
->set_x(bounds
->x() + static_cast<int>(
1002 (1 - static_cast<float>(ideal_delta
) / static_cast<float>(mini_delta
)) *
1003 (ideal_x
- bounds
->x())));
1006 void Tab::DataChanged(const TabRendererData
& old
) {
1007 if (data().media_state
!= old
.media_state
|| data().title
!= old
.title
)
1008 TooltipTextChanged();
1010 if (data().blocked
== old
.blocked
)
1019 void Tab::PaintTab(gfx::Canvas
* canvas
) {
1020 // See if the model changes whether the icons should be painted.
1021 const bool show_icon
= ShouldShowIcon();
1022 const bool show_media_indicator
= ShouldShowMediaIndicator();
1023 const bool show_close_button
= ShouldShowCloseBox();
1024 if (show_icon
!= showing_icon_
||
1025 show_media_indicator
!= showing_media_indicator_
||
1026 show_close_button
!= showing_close_button_
) {
1030 PaintTabBackground(canvas
);
1032 const SkColor title_color
= GetThemeProvider()->GetColor(IsSelected() ?
1033 ThemeProperties::COLOR_TAB_TEXT
:
1034 ThemeProperties::COLOR_BACKGROUND_TAB_TEXT
);
1035 title_
->SetVisible(!data().mini
||
1036 width() > kMiniTabRendererAsNormalTabWidth
);
1037 title_
->SetEnabledColor(title_color
);
1042 // If the close button color has changed, generate a new one.
1043 if (!close_button_color_
|| title_color
!= close_button_color_
) {
1044 close_button_color_
= title_color
;
1045 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
1046 close_button_
->SetBackground(close_button_color_
,
1047 rb
.GetImageSkiaNamed(IDR_CLOSE_1
),
1048 rb
.GetImageSkiaNamed(IDR_CLOSE_1_MASK
));
1052 void Tab::PaintImmersiveTab(gfx::Canvas
* canvas
) {
1053 // Use transparency for the draw-attention animation.
1055 if (pulse_animation_
&& pulse_animation_
->is_animating() && !data().mini
) {
1056 alpha
= pulse_animation_
->CurrentValueBetween(
1057 255, static_cast<int>(255 * kImmersiveTabMinThrobOpacity
));
1060 // Draw a gray rectangle to represent the tab. This works for mini-tabs as
1061 // well as regular ones. The active tab has a brigher bar.
1063 IsActive() ? kImmersiveActiveTabColor
: kImmersiveInactiveTabColor
;
1064 gfx::Rect bar_rect
= GetImmersiveBarRect();
1065 canvas
->FillRect(bar_rect
, SkColorSetA(color
, alpha
));
1067 // Paint network activity indicator.
1068 // TODO(jamescook): Replace this placeholder animation with a real one.
1069 // For now, let's go with a Cylon eye effect, but in blue.
1070 if (data().network_state
!= TabRendererData::NETWORK_STATE_NONE
) {
1071 const SkColor kEyeColor
= SkColorSetARGB(alpha
, 71, 138, 217);
1072 int eye_width
= bar_rect
.width() / 3;
1073 int eye_offset
= bar_rect
.width() * immersive_loading_step_
/
1074 kImmersiveLoadingStepCount
;
1075 if (eye_offset
+ eye_width
< bar_rect
.width()) {
1076 // Draw a single indicator strip because it fits inside |bar_rect|.
1078 bar_rect
.x() + eye_offset
, 0, eye_width
, kImmersiveBarHeight
);
1079 canvas
->FillRect(eye_rect
, kEyeColor
);
1081 // Draw two indicators to simulate the eye "wrapping around" to the left
1082 // side. The first part fills the remainder of the bar.
1083 int right_eye_width
= bar_rect
.width() - eye_offset
;
1084 gfx::Rect
right_eye_rect(
1085 bar_rect
.x() + eye_offset
, 0, right_eye_width
, kImmersiveBarHeight
);
1086 canvas
->FillRect(right_eye_rect
, kEyeColor
);
1087 // The second part parts the remaining |eye_width| on the left.
1088 int left_eye_width
= eye_offset
+ eye_width
- bar_rect
.width();
1089 gfx::Rect
left_eye_rect(
1090 bar_rect
.x(), 0, left_eye_width
, kImmersiveBarHeight
);
1091 canvas
->FillRect(left_eye_rect
, kEyeColor
);
1096 void Tab::PaintTabBackground(gfx::Canvas
* canvas
) {
1098 PaintActiveTabBackground(canvas
);
1100 if (mini_title_change_animation_
&&
1101 mini_title_change_animation_
->is_animating()) {
1102 PaintInactiveTabBackgroundWithTitleChange(canvas
);
1104 PaintInactiveTabBackground(canvas
);
1107 double throb_value
= GetThrobValue();
1108 if (throb_value
> 0) {
1109 canvas
->SaveLayerAlpha(static_cast<int>(throb_value
* 0xff),
1111 PaintActiveTabBackground(canvas
);
1117 void Tab::PaintInactiveTabBackgroundWithTitleChange(gfx::Canvas
* canvas
) {
1118 // Render the inactive tab background. We'll use this for clipping.
1119 gfx::Canvas
background_canvas(size(), canvas
->image_scale(), false);
1120 PaintInactiveTabBackground(&background_canvas
);
1122 gfx::ImageSkia
background_image(background_canvas
.ExtractImageRep());
1124 // Draw a radial gradient to hover_canvas.
1125 gfx::Canvas
hover_canvas(size(), canvas
->image_scale(), false);
1126 int radius
= kMiniTitleChangeGradientRadius
;
1127 int x0
= width() + radius
- kMiniTitleChangeInitialXOffset
;
1131 if (mini_title_change_animation_
->current_part_index() == 0) {
1132 x
= mini_title_change_animation_
->CurrentValueBetween(x0
, x1
);
1133 } else if (mini_title_change_animation_
->current_part_index() == 1) {
1136 x
= mini_title_change_animation_
->CurrentValueBetween(x1
, x2
);
1138 SkPoint center_point
;
1139 center_point
.iset(x
, 0);
1140 SkColor colors
[2] = { kMiniTitleChangeGradientColor1
,
1141 kMiniTitleChangeGradientColor2
};
1142 skia::RefPtr
<SkShader
> shader
= skia::AdoptRef(
1143 SkGradientShader::CreateRadial(
1144 center_point
, SkIntToScalar(radius
), colors
, NULL
, 2,
1145 SkShader::kClamp_TileMode
));
1147 paint
.setShader(shader
.get());
1148 hover_canvas
.DrawRect(gfx::Rect(x
- radius
, -radius
, radius
* 2, radius
* 2),
1151 // Draw the radial gradient clipped to the background into hover_image.
1152 gfx::ImageSkia hover_image
= gfx::ImageSkiaOperations::CreateMaskedImage(
1153 gfx::ImageSkia(hover_canvas
.ExtractImageRep()), background_image
);
1155 // Draw the tab background to the canvas.
1156 canvas
->DrawImageInt(background_image
, 0, 0);
1158 // And then the gradient on top of that.
1159 if (mini_title_change_animation_
->current_part_index() == 2) {
1160 uint8 alpha
= mini_title_change_animation_
->CurrentValueBetween(255, 0);
1161 canvas
->DrawImageInt(hover_image
, 0, 0, alpha
);
1163 canvas
->DrawImageInt(hover_image
, 0, 0);
1167 void Tab::PaintInactiveTabBackground(gfx::Canvas
* canvas
) {
1170 views::Widget
* widget
= GetWidget();
1171 GetTabIdAndFrameId(widget
, &tab_id
, &frame_id
);
1173 // Explicitly map the id so we cache correctly.
1174 const chrome::HostDesktopType host_desktop_type
= GetHostDesktopType(this);
1175 tab_id
= chrome::MapThemeImage(host_desktop_type
, tab_id
);
1177 // HasCustomImage() is only true if the theme provides the image. However,
1178 // even if the theme does not provide a tab background, the theme machinery
1179 // will make one if given a frame image.
1180 ui::ThemeProvider
* theme_provider
= GetThemeProvider();
1181 const bool theme_provided_image
= theme_provider
->HasCustomImage(tab_id
) ||
1182 (frame_id
!= 0 && theme_provider
->HasCustomImage(frame_id
));
1184 const bool can_cache
= !theme_provided_image
&&
1185 !hover_controller_
.ShouldDraw();
1188 ui::ScaleFactor scale_factor
=
1189 ui::GetSupportedScaleFactor(canvas
->image_scale());
1190 gfx::ImageSkia
cached_image(GetCachedImage(tab_id
, size(), scale_factor
));
1191 if (cached_image
.width() == 0) {
1192 gfx::Canvas
tmp_canvas(size(), canvas
->image_scale(), false);
1193 PaintInactiveTabBackgroundUsingResourceId(&tmp_canvas
, tab_id
);
1194 cached_image
= gfx::ImageSkia(tmp_canvas
.ExtractImageRep());
1195 SetCachedImage(tab_id
, scale_factor
, cached_image
);
1197 canvas
->DrawImageInt(cached_image
, 0, 0);
1199 PaintInactiveTabBackgroundUsingResourceId(canvas
, tab_id
);
1203 void Tab::PaintInactiveTabBackgroundUsingResourceId(gfx::Canvas
* canvas
,
1205 // WARNING: the inactive tab background may be cached. If you change what it
1206 // is drawn from you may need to update whether it can be cached.
1208 // The tab image needs to be lined up with the background image
1209 // so that it feels partially transparent. These offsets represent the tab
1210 // position within the frame background image.
1211 int offset
= GetMirroredX() + background_offset_
.x();
1213 gfx::ImageSkia
* tab_bg
= GetThemeProvider()->GetImageSkiaNamed(tab_id
);
1215 TabImage
* tab_image
= &tab_active_
;
1216 TabImage
* tab_inactive_image
= &tab_inactive_
;
1217 TabImage
* alpha
= &tab_alpha_
;
1219 // If the theme is providing a custom background image, then its top edge
1220 // should be at the top of the tab. Otherwise, we assume that the background
1221 // image is a composited foreground + frame image.
1222 int bg_offset_y
= GetThemeProvider()->HasCustomImage(tab_id
) ?
1223 0 : background_offset_
.y();
1225 // We need a gfx::Canvas object to be able to extract the image from.
1226 // We draw everything to this canvas and then output it to the canvas
1227 // parameter in addition to using it to mask the hover glow if needed.
1228 gfx::Canvas
background_canvas(size(), canvas
->image_scale(), false);
1230 // Draw left edge. Don't draw over the toolbar, as we're not the foreground
1232 gfx::ImageSkia tab_l
= gfx::ImageSkiaOperations::CreateTiledImage(
1233 *tab_bg
, offset
, bg_offset_y
, tab_image
->l_width
, height());
1234 gfx::ImageSkia theme_l
=
1235 gfx::ImageSkiaOperations::CreateMaskedImage(tab_l
, *alpha
->image_l
);
1236 background_canvas
.DrawImageInt(theme_l
,
1237 0, 0, theme_l
.width(), theme_l
.height() - kToolbarOverlap
,
1238 0, 0, theme_l
.width(), theme_l
.height() - kToolbarOverlap
,
1241 // Draw right edge. Again, don't draw over the toolbar.
1242 gfx::ImageSkia tab_r
= gfx::ImageSkiaOperations::CreateTiledImage(*tab_bg
,
1243 offset
+ width() - tab_image
->r_width
, bg_offset_y
,
1244 tab_image
->r_width
, height());
1245 gfx::ImageSkia theme_r
=
1246 gfx::ImageSkiaOperations::CreateMaskedImage(tab_r
, *alpha
->image_r
);
1247 background_canvas
.DrawImageInt(theme_r
,
1248 0, 0, theme_r
.width(), theme_r
.height() - kToolbarOverlap
,
1249 width() - theme_r
.width(), 0, theme_r
.width(),
1250 theme_r
.height() - kToolbarOverlap
, false);
1252 // Draw center. Instead of masking out the top portion we simply skip over
1253 // it by incrementing by GetDropShadowHeight(), since it's a simple
1254 // rectangle. And again, don't draw over the toolbar.
1255 background_canvas
.TileImageInt(*tab_bg
,
1256 offset
+ tab_image
->l_width
,
1257 bg_offset_y
+ kDropShadowHeight
,
1260 width() - tab_image
->l_width
- tab_image
->r_width
,
1261 height() - kDropShadowHeight
- kToolbarOverlap
);
1263 canvas
->DrawImageInt(
1264 gfx::ImageSkia(background_canvas
.ExtractImageRep()), 0, 0);
1266 if (!GetThemeProvider()->HasCustomImage(tab_id
) &&
1267 hover_controller_
.ShouldDraw()) {
1268 hover_controller_
.Draw(canvas
, gfx::ImageSkia(
1269 background_canvas
.ExtractImageRep()));
1272 // Now draw the highlights/shadows around the tab edge.
1273 canvas
->DrawImageInt(*tab_inactive_image
->image_l
, 0, 0);
1274 canvas
->TileImageInt(*tab_inactive_image
->image_c
,
1275 tab_inactive_image
->l_width
, 0,
1276 width() - tab_inactive_image
->l_width
-
1277 tab_inactive_image
->r_width
,
1279 canvas
->DrawImageInt(*tab_inactive_image
->image_r
,
1280 width() - tab_inactive_image
->r_width
, 0);
1283 void Tab::PaintActiveTabBackground(gfx::Canvas
* canvas
) {
1284 gfx::ImageSkia
* tab_background
=
1285 GetThemeProvider()->GetImageSkiaNamed(IDR_THEME_TOOLBAR
);
1286 int offset
= GetMirroredX() + background_offset_
.x();
1288 TabImage
* tab_image
= &tab_active_
;
1289 TabImage
* alpha
= &tab_alpha_
;
1292 gfx::ImageSkia tab_l
= gfx::ImageSkiaOperations::CreateTiledImage(
1293 *tab_background
, offset
, 0, tab_image
->l_width
, height());
1294 gfx::ImageSkia theme_l
=
1295 gfx::ImageSkiaOperations::CreateMaskedImage(tab_l
, *alpha
->image_l
);
1296 canvas
->DrawImageInt(theme_l
, 0, 0);
1299 gfx::ImageSkia tab_r
= gfx::ImageSkiaOperations::CreateTiledImage(
1301 offset
+ width() - tab_image
->r_width
, 0, tab_image
->r_width
, height());
1302 gfx::ImageSkia theme_r
=
1303 gfx::ImageSkiaOperations::CreateMaskedImage(tab_r
, *alpha
->image_r
);
1304 canvas
->DrawImageInt(theme_r
, width() - tab_image
->r_width
, 0);
1306 // Draw center. Instead of masking out the top portion we simply skip over it
1307 // by incrementing by GetDropShadowHeight(), since it's a simple rectangle.
1308 canvas
->TileImageInt(*tab_background
,
1309 offset
+ tab_image
->l_width
,
1313 width() - tab_image
->l_width
- tab_image
->r_width
,
1314 height() - kDropShadowHeight
);
1316 // Now draw the highlights/shadows around the tab edge.
1317 canvas
->DrawImageInt(*tab_image
->image_l
, 0, 0);
1318 canvas
->TileImageInt(*tab_image
->image_c
, tab_image
->l_width
, 0,
1319 width() - tab_image
->l_width
- tab_image
->r_width
, height());
1320 canvas
->DrawImageInt(*tab_image
->image_r
, width() - tab_image
->r_width
, 0);
1323 void Tab::PaintIcon(gfx::Canvas
* canvas
) {
1324 gfx::Rect bounds
= favicon_bounds_
;
1325 if (bounds
.IsEmpty())
1328 bounds
.set_x(GetMirroredXForRect(bounds
));
1330 if (data().network_state
!= TabRendererData::NETWORK_STATE_NONE
) {
1331 // Paint network activity (aka throbber) animation frame.
1332 ui::ThemeProvider
* tp
= GetThemeProvider();
1333 gfx::ImageSkia
frames(*tp
->GetImageSkiaNamed(
1334 (data().network_state
== TabRendererData::NETWORK_STATE_WAITING
) ?
1335 IDR_THROBBER_WAITING
: IDR_THROBBER
));
1337 int icon_size
= frames
.height();
1338 int image_offset
= loading_animation_frame_
* icon_size
;
1339 DrawIconCenter(canvas
, frames
, image_offset
,
1340 icon_size
, icon_size
,
1341 bounds
, false, SkPaint());
1342 } else if (should_display_crashed_favicon_
) {
1343 // Paint crash favicon.
1344 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
1345 gfx::ImageSkia
crashed_favicon(*rb
.GetImageSkiaNamed(IDR_SAD_FAVICON
));
1346 bounds
.set_y(bounds
.y() + favicon_hiding_offset_
);
1347 DrawIconCenter(canvas
, crashed_favicon
, 0,
1348 crashed_favicon
.width(),
1349 crashed_favicon
.height(),
1350 bounds
, true, SkPaint());
1351 } else if (!data().favicon
.isNull()) {
1352 // Paint the normal favicon.
1353 DrawIconCenter(canvas
, data().favicon
, 0,
1354 data().favicon
.width(),
1355 data().favicon
.height(),
1356 bounds
, true, SkPaint());
1360 void Tab::AdvanceLoadingAnimation(TabRendererData::NetworkState old_state
,
1361 TabRendererData::NetworkState state
) {
1362 // TODO(robliao): Remove ScopedTracker below once crbug.com/461137 is fixed.
1363 tracked_objects::ScopedTracker
tracking_profile1(
1364 FROM_HERE_WITH_EXPLICIT_FUNCTION("461137 Tab::AdvanceLoadingAnimation1"));
1365 static bool initialized
= false;
1366 static int loading_animation_frame_count
= 0;
1367 static int waiting_animation_frame_count
= 0;
1368 static int waiting_to_loading_frame_count_ratio
= 0;
1370 // TODO(robliao): Remove ScopedTracker below once crbug.com/461137 is fixed.
1371 tracked_objects::ScopedTracker
tracking_profile2(
1372 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1373 "461137 Tab::AdvanceLoadingAnimation2"));
1375 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
1376 gfx::ImageSkia
loading_animation(*rb
.GetImageSkiaNamed(IDR_THROBBER
));
1377 loading_animation_frame_count
=
1378 loading_animation
.width() / loading_animation
.height();
1379 gfx::ImageSkia
waiting_animation(*rb
.GetImageSkiaNamed(
1380 IDR_THROBBER_WAITING
));
1381 waiting_animation_frame_count
=
1382 waiting_animation
.width() / waiting_animation
.height();
1383 waiting_to_loading_frame_count_ratio
=
1384 waiting_animation_frame_count
/ loading_animation_frame_count
;
1386 base::debug::Alias(&loading_animation_frame_count
);
1387 base::debug::Alias(&waiting_animation_frame_count
);
1388 CHECK_NE(0, waiting_to_loading_frame_count_ratio
) <<
1389 "Number of frames in IDR_THROBBER must be equal to or greater " <<
1390 "than the number of frames in IDR_THROBBER_WAITING. Please " <<
1391 "investigate how this happened and update http://crbug.com/132590, " <<
1392 "this is causing crashes in the wild.";
1395 // The waiting animation is the reverse of the loading animation, but at a
1396 // different rate - the following reverses and scales the animation_frame_
1397 // so that the frame is at an equivalent position when going from one
1398 // animation to the other.
1399 if (state
!= old_state
) {
1400 loading_animation_frame_
= loading_animation_frame_count
-
1401 (loading_animation_frame_
/ waiting_to_loading_frame_count_ratio
);
1404 if (state
== TabRendererData::NETWORK_STATE_WAITING
) {
1405 loading_animation_frame_
= (loading_animation_frame_
+ 1) %
1406 waiting_animation_frame_count
;
1407 // Waiting steps backwards.
1408 immersive_loading_step_
=
1409 (immersive_loading_step_
- 1 + kImmersiveLoadingStepCount
) %
1410 kImmersiveLoadingStepCount
;
1411 } else if (state
== TabRendererData::NETWORK_STATE_LOADING
) {
1412 loading_animation_frame_
= (loading_animation_frame_
+ 1) %
1413 loading_animation_frame_count
;
1414 immersive_loading_step_
= (immersive_loading_step_
+ 1) %
1415 kImmersiveLoadingStepCount
;
1417 loading_animation_frame_
= 0;
1418 immersive_loading_step_
= 0;
1420 if (controller_
->IsImmersiveStyle()) {
1421 // TODO(robliao): Remove ScopedTracker below once crbug.com/461137 is fixed.
1422 tracked_objects::ScopedTracker
tracking_profile3(
1423 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1424 "461137 Tab::AdvanceLoadingAnimation3"));
1425 SchedulePaintInRect(GetImmersiveBarRect());
1427 // TODO(robliao): Remove ScopedTracker below once crbug.com/461137 is fixed.
1428 tracked_objects::ScopedTracker
tracking_profile4(
1429 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1430 "461137 Tab::AdvanceLoadingAnimation4"));
1431 ScheduleIconPaint();
1435 int Tab::IconCapacity() const {
1436 if (height() < GetMinimumUnselectedSize().height())
1438 const int available_width
=
1439 std::max(0, width() - kLeftPadding
- kRightPadding
);
1440 const int width_per_icon
= gfx::kFaviconSize
;
1441 const int kPaddingBetweenIcons
= 2;
1442 if (available_width
>= width_per_icon
&&
1443 available_width
< (width_per_icon
+ kPaddingBetweenIcons
)) {
1446 return available_width
/ (width_per_icon
+ kPaddingBetweenIcons
);
1449 bool Tab::ShouldShowIcon() const {
1450 return chrome::ShouldTabShowFavicon(
1451 IconCapacity(), data().mini
, IsActive(), data().show_icon
,
1452 media_indicator_button_
? media_indicator_button_
->showing_media_state() :
1456 bool Tab::ShouldShowMediaIndicator() const {
1457 return chrome::ShouldTabShowMediaIndicator(
1458 IconCapacity(), data().mini
, IsActive(), data().show_icon
,
1459 media_indicator_button_
? media_indicator_button_
->showing_media_state() :
1463 bool Tab::ShouldShowCloseBox() const {
1464 if (!IsActive() && controller_
->ShouldHideCloseButtonForInactiveTab(this))
1467 return chrome::ShouldTabShowCloseButton(
1468 IconCapacity(), data().mini
, IsActive());
1471 double Tab::GetThrobValue() {
1472 const bool is_selected
= IsSelected();
1473 const double min
= is_selected
? kSelectedTabOpacity
: 0;
1474 const double scale
= is_selected
? kSelectedTabThrobScale
: 1;
1476 // Showing both the pulse and title change animation at the same time is too
1478 if (pulse_animation_
&& pulse_animation_
->is_animating() &&
1479 (!mini_title_change_animation_
||
1480 !mini_title_change_animation_
->is_animating())) {
1481 return pulse_animation_
->GetCurrentValue() * kHoverOpacity
* scale
+ min
;
1484 if (hover_controller_
.ShouldDraw()) {
1485 return kHoverOpacity
* hover_controller_
.GetAnimationValue() * scale
+
1489 return is_selected
? kSelectedTabOpacity
: 0;
1492 void Tab::SetFaviconHidingOffset(int offset
) {
1493 favicon_hiding_offset_
= offset
;
1494 ScheduleIconPaint();
1497 void Tab::DisplayCrashedFavicon() {
1498 should_display_crashed_favicon_
= true;
1501 void Tab::ResetCrashedFavicon() {
1502 should_display_crashed_favicon_
= false;
1505 void Tab::StopCrashAnimation() {
1506 crash_icon_animation_
.reset();
1509 void Tab::StartCrashAnimation() {
1510 crash_icon_animation_
.reset(new FaviconCrashAnimation(this));
1511 crash_icon_animation_
->Start();
1514 bool Tab::IsPerformingCrashAnimation() const {
1515 return crash_icon_animation_
.get() && data_
.IsCrashed();
1518 void Tab::ScheduleIconPaint() {
1519 gfx::Rect bounds
= favicon_bounds_
;
1520 if (bounds
.IsEmpty())
1523 // Extends the area to the bottom when sad_favicon is animating.
1524 if (IsPerformingCrashAnimation())
1525 bounds
.set_height(height() - bounds
.y());
1526 bounds
.set_x(GetMirroredXForRect(bounds
));
1527 SchedulePaintInRect(bounds
);
1530 gfx::Rect
Tab::GetImmersiveBarRect() const {
1531 // The main bar is as wide as the normal tab's horizontal top line.
1532 // This top line of the tab extends a few pixels left and right of the
1533 // center image due to pixels in the rounded corner images.
1534 const int kBarPadding
= 1;
1535 int main_bar_left
= tab_active_
.l_width
- kBarPadding
;
1536 int main_bar_right
= width() - tab_active_
.r_width
+ kBarPadding
;
1538 main_bar_left
, 0, main_bar_right
- main_bar_left
, kImmersiveBarHeight
);
1541 void Tab::GetTabIdAndFrameId(views::Widget
* widget
,
1543 int* frame_id
) const {
1545 widget
->GetTopLevelWidget()->ShouldWindowContentsBeTransparent()) {
1546 *tab_id
= IDR_THEME_TAB_BACKGROUND_V
;
1548 } else if (data().incognito
) {
1549 *tab_id
= IDR_THEME_TAB_BACKGROUND_INCOGNITO
;
1550 *frame_id
= IDR_THEME_FRAME_INCOGNITO
;
1552 *tab_id
= IDR_THEME_TAB_BACKGROUND
;
1553 *frame_id
= IDR_THEME_FRAME
;
1557 MediaIndicatorButton
* Tab::GetMediaIndicatorButton() {
1558 if (!media_indicator_button_
) {
1559 media_indicator_button_
= new MediaIndicatorButton();
1560 AddChildView(media_indicator_button_
); // Takes ownership.
1562 return media_indicator_button_
;
1565 ////////////////////////////////////////////////////////////////////////////////
1566 // Tab, private static:
1569 void Tab::InitTabResources() {
1570 static bool initialized
= false;
1575 image_cache_
= new ImageCache();
1577 // Load the tab images once now, and maybe again later if the theme changes.
1582 void Tab::LoadTabImages() {
1583 // We're not letting people override tab images just yet.
1584 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
1586 tab_alpha_
.image_l
= rb
.GetImageSkiaNamed(IDR_TAB_ALPHA_LEFT
);
1587 tab_alpha_
.image_r
= rb
.GetImageSkiaNamed(IDR_TAB_ALPHA_RIGHT
);
1589 tab_active_
.image_l
= rb
.GetImageSkiaNamed(IDR_TAB_ACTIVE_LEFT
);
1590 tab_active_
.image_c
= rb
.GetImageSkiaNamed(IDR_TAB_ACTIVE_CENTER
);
1591 tab_active_
.image_r
= rb
.GetImageSkiaNamed(IDR_TAB_ACTIVE_RIGHT
);
1592 tab_active_
.l_width
= tab_active_
.image_l
->width();
1593 tab_active_
.r_width
= tab_active_
.image_r
->width();
1595 tab_inactive_
.image_l
= rb
.GetImageSkiaNamed(IDR_TAB_INACTIVE_LEFT
);
1596 tab_inactive_
.image_c
= rb
.GetImageSkiaNamed(IDR_TAB_INACTIVE_CENTER
);
1597 tab_inactive_
.image_r
= rb
.GetImageSkiaNamed(IDR_TAB_INACTIVE_RIGHT
);
1598 tab_inactive_
.l_width
= tab_inactive_
.image_l
->width();
1599 tab_inactive_
.r_width
= tab_inactive_
.image_r
->width();
1603 gfx::ImageSkia
Tab::GetCachedImage(int resource_id
,
1604 const gfx::Size
& size
,
1605 ui::ScaleFactor scale_factor
) {
1606 for (ImageCache::const_iterator i
= image_cache_
->begin();
1607 i
!= image_cache_
->end(); ++i
) {
1608 if (i
->resource_id
== resource_id
&& i
->scale_factor
== scale_factor
&&
1609 i
->image
.size() == size
) {
1613 return gfx::ImageSkia();
1617 void Tab::SetCachedImage(int resource_id
,
1618 ui::ScaleFactor scale_factor
,
1619 const gfx::ImageSkia
& image
) {
1620 DCHECK_NE(scale_factor
, ui::SCALE_FACTOR_NONE
);
1621 ImageCacheEntry entry
;
1622 entry
.resource_id
= resource_id
;
1623 entry
.scale_factor
= scale_factor
;
1624 entry
.image
= image
;
1625 image_cache_
->push_front(entry
);
1626 if (image_cache_
->size() > kMaxImageCacheSize
)
1627 image_cache_
->pop_back();