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/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/base/l10n/l10n_util.h"
29 #include "ui/base/models/list_selection_model.h"
30 #include "ui/base/resource/resource_bundle.h"
31 #include "ui/base/theme_provider.h"
32 #include "ui/gfx/animation/animation_container.h"
33 #include "ui/gfx/animation/multi_animation.h"
34 #include "ui/gfx/animation/throb_animation.h"
35 #include "ui/gfx/canvas.h"
36 #include "ui/gfx/color_analysis.h"
37 #include "ui/gfx/favicon_size.h"
38 #include "ui/gfx/font.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/rect_based_targeting_utils.h"
47 #include "ui/views/widget/tooltip_manager.h"
48 #include "ui/views/widget/widget.h"
49 #include "ui/views/window/non_client_view.h"
52 #include "ui/aura/env.h"
57 // Padding around the "content" of a tab, occupied by the tab border graphics.
58 const int kLeftPadding
= 22;
59 const int kTopPadding
= 7;
60 const int kRightPadding
= 17;
61 const int kBottomPadding
= 5;
63 // Height of the shadow at the top of the tab image assets.
64 const int kDropShadowHeight
= 4;
66 // How long the pulse throb takes.
67 const int kPulseDurationMs
= 200;
69 // Width of touch tabs.
70 static const int kTouchWidth
= 120;
72 static const int kToolbarOverlap
= 1;
73 static const int kFaviconTitleSpacing
= 4;
74 // Additional vertical offset for title text relative to top of tab.
75 // Ash text rendering may be different than Windows.
76 static const int kTitleTextOffsetYAsh
= 1;
77 static const int kTitleTextOffsetY
= 0;
78 static const int kTitleCloseButtonSpacing
= 3;
79 static const int kStandardTitleWidth
= 175;
80 // Additional vertical offset for close button relative to top of tab.
81 // Ash needs this to match the text vertical position.
82 static const int kCloseButtonVertFuzzAsh
= 1;
83 static const int kCloseButtonVertFuzz
= 0;
84 // Additional horizontal offset for close button relative to title text.
85 static const int kCloseButtonHorzFuzz
= 3;
87 // When a non-mini-tab becomes a mini-tab the width of the tab animates. If
88 // the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab
89 // is rendered as a normal tab. This is done to avoid having the title
90 // immediately disappear when transitioning a tab from normal to mini-tab.
91 static const int kMiniTabRendererAsNormalTabWidth
=
92 browser_defaults::kMiniTabWidth
+ 30;
94 // How opaque to make the hover state (out of 1).
95 static const double kHoverOpacity
= 0.33;
97 // Opacity for non-active selected tabs.
98 static const double kSelectedTabOpacity
= .45;
100 // Selected (but not active) tabs have their throb value scaled down by this.
101 static const double kSelectedTabThrobScale
= .5;
103 // Durations for the various parts of the mini tab title animation.
104 static const int kMiniTitleChangeAnimationDuration1MS
= 1600;
105 static const int kMiniTitleChangeAnimationStart1MS
= 0;
106 static const int kMiniTitleChangeAnimationEnd1MS
= 1900;
107 static const int kMiniTitleChangeAnimationDuration2MS
= 0;
108 static const int kMiniTitleChangeAnimationDuration3MS
= 550;
109 static const int kMiniTitleChangeAnimationStart3MS
= 150;
110 static const int kMiniTitleChangeAnimationEnd3MS
= 800;
111 static const int kMiniTitleChangeAnimationIntervalMS
= 40;
113 // Offset from the right edge for the start of the mini title change animation.
114 static const int kMiniTitleChangeInitialXOffset
= 6;
116 // Radius of the radial gradient used for mini title change animation.
117 static const int kMiniTitleChangeGradientRadius
= 20;
119 // Colors of the gradient used during the mini title change animation.
120 static const SkColor kMiniTitleChangeGradientColor1
= SK_ColorWHITE
;
121 static const SkColor kMiniTitleChangeGradientColor2
=
122 SkColorSetARGB(0, 255, 255, 255);
124 // Max number of images to cache. This has to be at least two since rounding
125 // errors may lead to tabs in the same tabstrip having different sizes.
126 const size_t kMaxImageCacheSize
= 4;
128 // Height of the miniature tab strip in immersive mode.
129 const int kImmersiveTabHeight
= 3;
131 // Height of the small tab indicator rectangles in immersive mode.
132 const int kImmersiveBarHeight
= 2;
134 // Color for active and inactive tabs in the immersive mode light strip. These
135 // should be a little brighter than the color of the normal art assets for tabs,
136 // which for active tabs is 230, 230, 230 and for inactive is 184, 184, 184.
137 const SkColor kImmersiveActiveTabColor
= SkColorSetRGB(235, 235, 235);
138 const SkColor kImmersiveInactiveTabColor
= SkColorSetRGB(190, 190, 190);
140 // The minimum opacity (out of 1) when a tab (either active or inactive) is
141 // throbbing in the immersive mode light strip.
142 const double kImmersiveTabMinThrobOpacity
= 0.66;
144 // Number of steps in the immersive mode loading animation.
145 const int kImmersiveLoadingStepCount
= 32;
147 const char kTabCloseButtonName
[] = "TabCloseButton";
149 void DrawIconAtLocation(gfx::Canvas
* canvas
,
150 const gfx::ImageSkia
& image
,
157 const SkPaint
& paint
) {
158 // NOTE: the clipping is a work around for 69528, it shouldn't be necessary.
160 canvas
->ClipRect(gfx::Rect(dst_x
, dst_y
, icon_width
, icon_height
));
161 canvas
->DrawImageInt(image
,
162 image_offset
, 0, icon_width
, icon_height
,
163 dst_x
, dst_y
, icon_width
, icon_height
,
168 // Draws the icon image at the center of |bounds|.
169 void DrawIconCenter(gfx::Canvas
* canvas
,
170 const gfx::ImageSkia
& image
,
174 const gfx::Rect
& bounds
,
176 const SkPaint
& paint
) {
177 // Center the image within bounds.
178 int dst_x
= bounds
.x() - (icon_width
- bounds
.width()) / 2;
179 int dst_y
= bounds
.y() - (icon_height
- bounds
.height()) / 2;
180 DrawIconAtLocation(canvas
, image
, image_offset
, dst_x
, dst_y
, icon_width
,
181 icon_height
, filter
, paint
);
184 chrome::HostDesktopType
GetHostDesktopType(views::View
* view
) {
185 // Widget is NULL when tabs are detached.
186 views::Widget
* widget
= view
->GetWidget();
187 return chrome::GetHostDesktopTypeForNativeView(
188 widget
? widget
->GetNativeView() : NULL
);
193 ////////////////////////////////////////////////////////////////////////////////
194 // FaviconCrashAnimation
196 // A custom animation subclass to manage the favicon crash animation.
197 class Tab::FaviconCrashAnimation
: public gfx::LinearAnimation
,
198 public gfx::AnimationDelegate
{
200 explicit FaviconCrashAnimation(Tab
* target
)
201 : gfx::LinearAnimation(1000, 25, this),
204 virtual ~FaviconCrashAnimation() {}
206 // gfx::Animation overrides:
207 virtual void AnimateToState(double state
) OVERRIDE
{
208 const double kHidingOffset
= 27;
211 target_
->SetFaviconHidingOffset(
212 static_cast<int>(floor(kHidingOffset
* 2.0 * state
)));
214 target_
->DisplayCrashedFavicon();
215 target_
->SetFaviconHidingOffset(
217 floor(kHidingOffset
- ((state
- .5) * 2.0 * kHidingOffset
))));
221 // gfx::AnimationDelegate overrides:
222 virtual void AnimationCanceled(const gfx::Animation
* animation
) OVERRIDE
{
223 target_
->SetFaviconHidingOffset(0);
229 DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation
);
232 ////////////////////////////////////////////////////////////////////////////////
235 // This is a Button subclass that causes middle clicks to be forwarded to the
236 // parent View by explicitly not handling them in OnMousePressed.
237 class Tab::TabCloseButton
: public views::ImageButton
{
239 explicit TabCloseButton(Tab
* tab
) : views::ImageButton(tab
), tab_(tab
) {}
240 virtual ~TabCloseButton() {}
242 // Overridden from views::View.
243 virtual View
* GetEventHandlerForRect(const gfx::Rect
& rect
) OVERRIDE
{
244 if (!views::UsePointBasedTargeting(rect
))
245 return View::GetEventHandlerForRect(rect
);
247 // Ignore the padding set on the button.
248 gfx::Rect contents_bounds
= GetContentsBounds();
249 contents_bounds
.set_x(GetMirroredXForRect(contents_bounds
));
251 // TODO(tdanderson): Remove this ifdef if rect-based targeting
252 // is turned on by default.
254 // Include the padding in hit-test for touch events.
255 if (aura::Env::GetInstance()->is_touch_down())
256 contents_bounds
= GetLocalBounds();
257 #elif defined(OS_WIN)
258 // TODO(sky): Use local-bounds if a touch-point is active.
259 // http://crbug.com/145258
262 return contents_bounds
.Intersects(rect
) ? this : parent();
265 // Overridden from views::View.
266 virtual View
* GetTooltipHandlerForPoint(const gfx::Point
& point
) OVERRIDE
{
267 // Tab close button has no children, so tooltip handler should be the same
268 // as the event handler.
269 // In addition, a hit test has to be performed for the point (as
270 // GetTooltipHandlerForPoint() is responsible for it).
271 if (!HitTestPoint(point
))
273 return GetEventHandlerForPoint(point
);
276 virtual bool OnMousePressed(const ui::MouseEvent
& event
) OVERRIDE
{
277 tab_
->controller_
->OnMouseEventInTab(this, event
);
279 bool handled
= ImageButton::OnMousePressed(event
);
280 // Explicitly mark midle-mouse clicks as non-handled to ensure the tab
282 return event
.IsOnlyMiddleMouseButton() ? false : handled
;
285 virtual void OnMouseMoved(const ui::MouseEvent
& event
) OVERRIDE
{
286 tab_
->controller_
->OnMouseEventInTab(this, event
);
287 CustomButton::OnMouseMoved(event
);
290 virtual void OnMouseReleased(const ui::MouseEvent
& event
) OVERRIDE
{
291 tab_
->controller_
->OnMouseEventInTab(this, event
);
292 CustomButton::OnMouseReleased(event
);
295 virtual void OnGestureEvent(ui::GestureEvent
* event
) OVERRIDE
{
296 // Consume all gesture events here so that the parent (Tab) does not
297 // start consuming gestures.
298 ImageButton::OnGestureEvent(event
);
302 virtual bool HasHitTestMask() const OVERRIDE
{
306 virtual void GetHitTestMask(HitTestSource source
,
307 gfx::Path
* path
) const OVERRIDE
{
308 // Use the button's contents bounds (which does not include padding)
309 // and the hit test mask of our parent |tab_| to determine if the
310 // button is hidden behind another tab.
312 tab_
->GetHitTestMask(source
, &tab_mask
);
314 gfx::Rect
button_bounds(GetContentsBounds());
315 button_bounds
.set_x(GetMirroredXForRect(button_bounds
));
316 gfx::RectF
tab_bounds_f(gfx::SkRectToRectF(tab_mask
.getBounds()));
317 views::View::ConvertRectToTarget(tab_
, this, &tab_bounds_f
);
318 gfx::Rect tab_bounds
= gfx::ToEnclosingRect(tab_bounds_f
);
320 // If either the top or bottom of the tab close button is clipped,
321 // do not consider these regions to be part of the button's bounds.
322 int top_overflow
= tab_bounds
.y() - button_bounds
.y();
323 int bottom_overflow
= button_bounds
.bottom() - tab_bounds
.bottom();
324 if (top_overflow
> 0)
325 button_bounds
.set_y(tab_bounds
.y());
326 else if (bottom_overflow
> 0)
327 button_bounds
.set_height(button_bounds
.height() - bottom_overflow
);
329 // If the hit test request is in response to a gesture, |path| should be
330 // empty unless the entire tab close button is visible to the user. Hit
331 // test requests in response to a mouse event should always set |path|
332 // to be the visible portion of the tab close button, even if it is
333 // partially hidden behind another tab.
335 gfx::Rect
intersection(gfx::IntersectRects(tab_bounds
, button_bounds
));
336 if (!intersection
.IsEmpty()) {
337 // TODO(tdanderson): Consider always returning the intersection if
338 // the non-rectangular shape of the tabs can be accounted for.
339 if (source
== HIT_TEST_SOURCE_TOUCH
&&
340 !tab_bounds
.Contains(button_bounds
))
343 path
->addRect(RectToSkRect(intersection
));
347 virtual const char* GetClassName() const OVERRIDE
{
348 return kTabCloseButtonName
;
354 DISALLOW_COPY_AND_ASSIGN(TabCloseButton
);
357 ////////////////////////////////////////////////////////////////////////////////
360 Tab::ImageCacheEntry::ImageCacheEntry()
362 scale_factor(ui::SCALE_FACTOR_NONE
) {
365 Tab::ImageCacheEntry::~ImageCacheEntry() {}
367 ////////////////////////////////////////////////////////////////////////////////
371 const char Tab::kViewClassName
[] = "Tab";
374 Tab::TabImage
Tab::tab_alpha_
= {0};
375 Tab::TabImage
Tab::tab_active_
= {0};
376 Tab::TabImage
Tab::tab_inactive_
= {0};
378 gfx::Font
* Tab::font_
= NULL
;
380 int Tab::font_height_
= 0;
382 Tab::ImageCache
* Tab::image_cache_
= NULL
;
384 ////////////////////////////////////////////////////////////////////////////////
387 Tab::Tab(TabController
* controller
)
388 : controller_(controller
),
391 favicon_hiding_offset_(0),
392 loading_animation_frame_(0),
393 immersive_loading_step_(0),
394 should_display_crashed_favicon_(false),
395 animating_media_state_(TAB_MEDIA_STATE_NONE
),
396 tab_activated_with_last_gesture_begin_(false),
397 hover_controller_(this),
398 showing_icon_(false),
399 showing_media_indicator_(false),
400 showing_close_button_(false),
401 close_button_color_(0) {
405 // So we get don't get enter/exit on children and don't prematurely stop the
407 set_notify_enter_exit_on_child(true);
411 // Add the Close Button.
412 close_button_
= new TabCloseButton(this);
413 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
414 close_button_
->SetImage(views::CustomButton::STATE_NORMAL
,
415 rb
.GetImageSkiaNamed(IDR_CLOSE_1
));
416 close_button_
->SetImage(views::CustomButton::STATE_HOVERED
,
417 rb
.GetImageSkiaNamed(IDR_CLOSE_1_H
));
418 close_button_
->SetImage(views::CustomButton::STATE_PRESSED
,
419 rb
.GetImageSkiaNamed(IDR_CLOSE_1_P
));
420 close_button_
->SetAccessibleName(
421 l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE
));
422 // Disable animation so that the red danger sign shows up immediately
423 // to help avoid mis-clicks.
424 close_button_
->SetAnimationDuration(0);
425 AddChildView(close_button_
);
427 set_context_menu_controller(this);
433 void Tab::set_animation_container(gfx::AnimationContainer
* container
) {
434 animation_container_
= container
;
435 hover_controller_
.SetAnimationContainer(container
);
438 bool Tab::IsActive() const {
439 return controller_
->IsActiveTab(this);
442 bool Tab::IsSelected() const {
443 return controller_
->IsTabSelected(this);
446 void Tab::SetData(const TabRendererData
& data
) {
447 if (data_
.Equals(data
))
450 TabRendererData
old(data_
);
453 if (data_
.IsCrashed()) {
454 if (!should_display_crashed_favicon_
&& !IsPerformingCrashAnimation()) {
455 data_
.media_state
= TAB_MEDIA_STATE_NONE
;
456 #if defined(OS_CHROMEOS)
457 // On Chrome OS, we reload killed tabs automatically when the user
458 // switches to them. Don't display animations for these unless they're
459 // selected (i.e. in the foreground) -- we won't reload these
460 // automatically since we don't want to get into a crash loop.
462 data_
.crashed_status
!= base::TERMINATION_STATUS_PROCESS_WAS_KILLED
)
463 StartCrashAnimation();
465 StartCrashAnimation();
469 if (IsPerformingCrashAnimation())
470 StopCrashAnimation();
471 ResetCrashedFavicon();
474 if (data_
.media_state
!= old
.media_state
) {
475 if (data_
.media_state
!= TAB_MEDIA_STATE_NONE
)
476 animating_media_state_
= data_
.media_state
;
477 StartMediaIndicatorAnimation();
480 if (old
.mini
!= data_
.mini
) {
481 if (tab_animation_
.get() && tab_animation_
->is_animating()) {
482 tab_animation_
->Stop();
483 tab_animation_
.reset(NULL
);
493 void Tab::UpdateLoadingAnimation(TabRendererData::NetworkState state
) {
494 if (state
== data_
.network_state
&&
495 state
== TabRendererData::NETWORK_STATE_NONE
) {
496 // If the network state is none and hasn't changed, do nothing. Otherwise we
497 // need to advance the animation frame.
501 TabRendererData::NetworkState old_state
= data_
.network_state
;
502 data_
.network_state
= state
;
503 AdvanceLoadingAnimation(old_state
, state
);
506 void Tab::StartPulse() {
507 gfx::ThrobAnimation
* animation
= new gfx::ThrobAnimation(this);
508 animation
->SetSlideDuration(kPulseDurationMs
);
509 if (animation_container_
.get())
510 animation
->SetContainer(animation_container_
.get());
511 animation
->StartThrobbing(std::numeric_limits
<int>::max());
512 tab_animation_
.reset(animation
);
515 void Tab::StopPulse() {
516 if (!tab_animation_
.get())
518 tab_animation_
->Stop();
519 tab_animation_
.reset(NULL
);
522 void Tab::StartMiniTabTitleAnimation() {
523 // We can only do this animation if the tab is mini because we will
524 // upcast tab_animation back to MultiAnimation when we draw.
527 if (!tab_animation_
.get()) {
528 gfx::MultiAnimation::Parts parts
;
530 gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration1MS
,
531 gfx::Tween::EASE_OUT
));
533 gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration2MS
,
536 gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration3MS
,
537 gfx::Tween::EASE_IN
));
538 parts
[0].start_time_ms
= kMiniTitleChangeAnimationStart1MS
;
539 parts
[0].end_time_ms
= kMiniTitleChangeAnimationEnd1MS
;
540 parts
[2].start_time_ms
= kMiniTitleChangeAnimationStart3MS
;
541 parts
[2].end_time_ms
= kMiniTitleChangeAnimationEnd3MS
;
542 base::TimeDelta timeout
=
543 base::TimeDelta::FromMilliseconds(kMiniTitleChangeAnimationIntervalMS
);
544 gfx::MultiAnimation
* animation
= new gfx::MultiAnimation(parts
, timeout
);
545 if (animation_container_
.get())
546 animation
->SetContainer(animation_container_
.get());
547 animation
->set_delegate(this);
548 tab_animation_
.reset(animation
);
550 tab_animation_
->Start();
553 void Tab::StopMiniTabTitleAnimation() {
554 if (!tab_animation_
.get())
556 tab_animation_
->Stop();
557 tab_animation_
.reset(NULL
);
561 gfx::Size
Tab::GetBasicMinimumUnselectedSize() {
564 gfx::Size minimum_size
;
565 minimum_size
.set_width(kLeftPadding
+ kRightPadding
);
566 // Since we use image images, the real minimum height of the image is
567 // defined most accurately by the height of the end cap images.
568 minimum_size
.set_height(tab_active_
.image_l
->height());
572 gfx::Size
Tab::GetMinimumUnselectedSize() {
573 return GetBasicMinimumUnselectedSize();
577 gfx::Size
Tab::GetMinimumSelectedSize() {
578 gfx::Size minimum_size
= GetBasicMinimumUnselectedSize();
579 minimum_size
.set_width(
580 kLeftPadding
+ gfx::kFaviconSize
+ kRightPadding
);
585 gfx::Size
Tab::GetStandardSize() {
586 gfx::Size standard_size
= GetBasicMinimumUnselectedSize();
587 standard_size
.set_width(
588 standard_size
.width() + kFaviconTitleSpacing
+ kStandardTitleWidth
);
589 return standard_size
;
593 int Tab::GetTouchWidth() {
598 int Tab::GetMiniWidth() {
599 return browser_defaults::kMiniTabWidth
;
603 int Tab::GetImmersiveHeight() {
604 return kImmersiveTabHeight
;
607 ////////////////////////////////////////////////////////////////////////////////
608 // Tab, AnimationDelegate overrides:
610 void Tab::AnimationProgressed(const gfx::Animation
* animation
) {
611 // Ignore if the pulse animation is being performed on active tab because
612 // it repaints the same image. See |Tab::PaintTabBackground()|.
613 if (animation
== tab_animation_
.get() && IsActive())
618 void Tab::AnimationCanceled(const gfx::Animation
* animation
) {
619 if (media_indicator_animation_
== animation
)
620 animating_media_state_
= data_
.media_state
;
624 void Tab::AnimationEnded(const gfx::Animation
* animation
) {
625 if (media_indicator_animation_
== animation
)
626 animating_media_state_
= data_
.media_state
;
630 ////////////////////////////////////////////////////////////////////////////////
631 // Tab, views::ButtonListener overrides:
633 void Tab::ButtonPressed(views::Button
* sender
, const ui::Event
& event
) {
634 const CloseTabSource source
=
635 (event
.type() == ui::ET_MOUSE_RELEASED
&&
636 (event
.flags() & ui::EF_FROM_TOUCH
) == 0) ? CLOSE_TAB_FROM_MOUSE
:
637 CLOSE_TAB_FROM_TOUCH
;
638 DCHECK_EQ(close_button_
, sender
);
639 controller_
->CloseTab(this, source
);
640 if (event
.type() == ui::ET_GESTURE_TAP
)
641 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_TABCLOSE_TAP
);
644 ////////////////////////////////////////////////////////////////////////////////
645 // Tab, views::ContextMenuController overrides:
647 void Tab::ShowContextMenuForView(views::View
* source
,
648 const gfx::Point
& point
,
649 ui::MenuSourceType source_type
) {
651 controller_
->ShowContextMenuForTab(this, point
, source_type
);
654 ////////////////////////////////////////////////////////////////////////////////
655 // Tab, views::View overrides:
657 void Tab::OnPaint(gfx::Canvas
* canvas
) {
658 // Don't paint if we're narrower than we can render correctly. (This should
659 // only happen during animations).
660 if (width() < GetMinimumUnselectedSize().width() && !data().mini
)
664 if (!controller_
->ShouldPaintTab(this, &clip
))
666 if (!clip
.IsEmpty()) {
668 canvas
->ClipRect(clip
);
671 if (controller_
->IsImmersiveStyle())
672 PaintImmersiveTab(canvas
);
681 gfx::Rect lb
= GetContentsBounds();
684 lb
.Inset(kLeftPadding
, kTopPadding
, kRightPadding
, kBottomPadding
);
686 // The height of the content of the Tab is the largest of the favicon,
687 // the title text and the close button graphic.
688 const int kTabIconSize
= gfx::kFaviconSize
;
689 int content_height
= std::max(kTabIconSize
, font_height_
);
690 close_button_
->SetBorder(views::Border::NullBorder());
691 gfx::Size
close_button_size(close_button_
->GetPreferredSize());
692 content_height
= std::max(content_height
, close_button_size
.height());
695 showing_icon_
= ShouldShowIcon();
697 // Use the size of the favicon as apps use a bigger favicon size.
698 int favicon_top
= kTopPadding
+ content_height
/ 2 - kTabIconSize
/ 2;
699 int favicon_left
= lb
.x();
700 favicon_bounds_
.SetRect(favicon_left
, favicon_top
,
701 kTabIconSize
, kTabIconSize
);
702 MaybeAdjustLeftForMiniTab(&favicon_bounds_
);
704 favicon_bounds_
.SetRect(lb
.x(), lb
.y(), 0, 0);
707 // Size the Close button.
708 showing_close_button_
= ShouldShowCloseBox();
709 const bool is_host_desktop_type_ash
=
710 GetHostDesktopType(this) == chrome::HOST_DESKTOP_TYPE_ASH
;
711 if (showing_close_button_
) {
712 const int close_button_vert_fuzz
= is_host_desktop_type_ash
?
713 kCloseButtonVertFuzzAsh
: kCloseButtonVertFuzz
;
714 int close_button_top
= kTopPadding
+ close_button_vert_fuzz
+
715 (content_height
- close_button_size
.height()) / 2;
716 // If the ratio of the close button size to tab width exceeds the maximum.
717 // The close button should be as large as possible so that there is a larger
718 // hit-target for touch events. So the close button bounds extends to the
719 // edges of the tab. However, the larger hit-target should be active only
720 // for mouse events, and the close-image should show up in the right place.
721 // So a border is added to the button with necessary padding. The close
722 // button (BaseTab::TabCloseButton) makes sure the padding is a hit-target
723 // only for touch events.
724 int top_border
= close_button_top
;
725 int bottom_border
= height() - (close_button_size
.height() + top_border
);
726 int left_border
= kCloseButtonHorzFuzz
;
727 int right_border
= width() - (lb
.width() + close_button_size
.width() +
729 close_button_
->SetBorder(views::Border::CreateEmptyBorder(
730 top_border
, left_border
, bottom_border
, right_border
));
731 close_button_
->SetPosition(gfx::Point(lb
.width(), 0));
732 close_button_
->SizeToPreferredSize();
733 close_button_
->SetVisible(true);
735 close_button_
->SetBounds(0, 0, 0, 0);
736 close_button_
->SetVisible(false);
739 showing_media_indicator_
= ShouldShowMediaIndicator();
740 if (showing_media_indicator_
) {
741 const gfx::Image
& media_indicator_image
=
742 chrome::GetTabMediaIndicatorImage(animating_media_state_
);
743 media_indicator_bounds_
.set_width(media_indicator_image
.Width());
744 media_indicator_bounds_
.set_height(media_indicator_image
.Height());
745 media_indicator_bounds_
.set_y(
747 (content_height
- media_indicator_bounds_
.height()) / 2);
748 const int right
= showing_close_button_
?
749 close_button_
->x() + close_button_
->GetInsets().left() : lb
.right();
750 media_indicator_bounds_
.set_x(
751 std::max(lb
.x(), right
- media_indicator_bounds_
.width()));
752 MaybeAdjustLeftForMiniTab(&media_indicator_bounds_
);
754 media_indicator_bounds_
.SetRect(lb
.x(), lb
.y(), 0, 0);
757 const int title_text_offset
= is_host_desktop_type_ash
?
758 kTitleTextOffsetYAsh
: kTitleTextOffsetY
;
759 int title_left
= favicon_bounds_
.right() + kFaviconTitleSpacing
;
760 int title_top
= kTopPadding
+ title_text_offset
+
761 (content_height
- font_height_
) / 2;
762 // Size the Title text to fill the remaining space.
763 if (!data().mini
|| width() >= kMiniTabRendererAsNormalTabWidth
) {
764 // If the user has big fonts, the title will appear rendered too far down
765 // on the y-axis if we use the regular top padding, so we need to adjust it
766 // so that the text appears centered.
767 gfx::Size minimum_size
= GetMinimumUnselectedSize();
768 int text_height
= title_top
+ font_height_
+ kBottomPadding
;
769 if (text_height
> minimum_size
.height())
770 title_top
-= (text_height
- minimum_size
.height()) / 2;
773 if (showing_media_indicator_
) {
774 title_width
= media_indicator_bounds_
.x() - kTitleCloseButtonSpacing
-
776 } else if (close_button_
->visible()) {
777 // The close button has an empty border with some padding (see details
778 // above where the close-button's bounds is set). Allow the title to
779 // overlap the empty padding.
780 title_width
= close_button_
->x() + close_button_
->GetInsets().left() -
781 kTitleCloseButtonSpacing
- title_left
;
783 title_width
= lb
.width() - title_left
;
785 title_width
= std::max(title_width
, 0);
786 title_bounds_
.SetRect(title_left
, title_top
, title_width
, font_height_
);
788 title_bounds_
.SetRect(title_left
, title_top
, 0, 0);
791 // Certain UI elements within the Tab (the favicon, etc.) are not represented
792 // as child Views (which is the preferred method). Instead, these UI elements
793 // are drawn directly on the canvas from within Tab::OnPaint(). The Tab's
794 // child Views (for example, the Tab's close button which is a views::Button
795 // instance) are automatically mirrored by the mirroring infrastructure in
796 // views. The elements Tab draws directly on the canvas need to be manually
797 // mirrored if the View's layout is right-to-left.
798 title_bounds_
.set_x(GetMirroredXForRect(title_bounds_
));
801 void Tab::OnThemeChanged() {
805 const char* Tab::GetClassName() const {
806 return kViewClassName
;
809 bool Tab::HasHitTestMask() const {
813 void Tab::GetHitTestMask(HitTestSource source
, gfx::Path
* path
) const {
814 // When the window is maximized we don't want to shave off the edges or top
815 // shadow of the tab, such that the user can click anywhere along the top
816 // edge of the screen to select a tab. Ditto for immersive fullscreen.
817 const views::Widget
* widget
= GetWidget();
818 bool include_top_shadow
=
819 widget
&& (widget
->IsMaximized() || widget
->IsFullscreen());
820 TabResources::GetHitTestMask(width(), height(), include_top_shadow
, path
);
822 // It is possible for a portion of the tab to be occluded if tabs are
823 // stacked, so modify the hit test mask to only include the visible
824 // region of the tab.
826 controller_
->ShouldPaintTab(this, &clip
);
827 if (clip
.size().GetArea()) {
828 SkRect
intersection(path
->getBounds());
829 intersection
.intersect(RectToSkRect(clip
));
831 path
->addRect(intersection
);
835 bool Tab::GetTooltipText(const gfx::Point
& p
, base::string16
* tooltip
) const {
836 // Note: Anything that affects the tooltip text should be accounted for when
837 // calling TooltipTextChanged() from Tab::DataChanged().
838 *tooltip
= chrome::AssembleTabTooltipText(data_
.title
, data_
.media_state
);
839 return !tooltip
->empty();
842 bool Tab::GetTooltipTextOrigin(const gfx::Point
& p
, gfx::Point
* origin
) const {
843 origin
->set_x(title_bounds_
.x() + 10);
844 origin
->set_y(-views::TooltipManager::GetTooltipHeight() - 4);
848 bool Tab::OnMousePressed(const ui::MouseEvent
& event
) {
849 controller_
->OnMouseEventInTab(this, event
);
851 // Allow a right click from touch to drag, which corresponds to a long click.
852 if (event
.IsOnlyLeftMouseButton() ||
853 (event
.IsOnlyRightMouseButton() && event
.flags() & ui::EF_FROM_TOUCH
)) {
854 ui::ListSelectionModel original_selection
;
855 original_selection
.Copy(controller_
->GetSelectionModel());
856 // Changing the selection may cause our bounds to change. If that happens
857 // the location of the event may no longer be valid. Create a copy of the
858 // event in the parents coordinate, which won't change, and recreate an
859 // event after changing so the coordinates are correct.
860 ui::MouseEvent
event_in_parent(event
, static_cast<View
*>(this), parent());
861 if (controller_
->SupportsMultipleSelection()) {
862 if (event
.IsShiftDown() && event
.IsControlDown()) {
863 controller_
->AddSelectionFromAnchorTo(this);
864 } else if (event
.IsShiftDown()) {
865 controller_
->ExtendSelectionTo(this);
866 } else if (event
.IsControlDown()) {
867 controller_
->ToggleSelected(this);
869 // Don't allow dragging non-selected tabs.
872 } else if (!IsSelected()) {
873 controller_
->SelectTab(this);
875 } else if (!IsSelected()) {
876 controller_
->SelectTab(this);
878 ui::MouseEvent
cloned_event(event_in_parent
, parent(),
879 static_cast<View
*>(this));
880 controller_
->MaybeStartDrag(this, cloned_event
, original_selection
);
885 bool Tab::OnMouseDragged(const ui::MouseEvent
& event
) {
886 controller_
->ContinueDrag(this, event
);
890 void Tab::OnMouseReleased(const ui::MouseEvent
& event
) {
891 controller_
->OnMouseEventInTab(this, event
);
893 // Notify the drag helper that we're done with any potential drag operations.
894 // Clean up the drag helper, which is re-created on the next mouse press.
895 // In some cases, ending the drag will schedule the tab for destruction; if
896 // so, bail immediately, since our members are already dead and we shouldn't
897 // do anything else except drop the tab where it is.
898 if (controller_
->EndDrag(END_DRAG_COMPLETE
))
901 // Close tab on middle click, but only if the button is released over the tab
902 // (normal windows behavior is to discard presses of a UI element where the
903 // releases happen off the element).
904 if (event
.IsMiddleMouseButton()) {
905 if (HitTestPoint(event
.location())) {
906 controller_
->CloseTab(this, CLOSE_TAB_FROM_MOUSE
);
907 } else if (closing_
) {
908 // We're animating closed and a middle mouse button was pushed on us but
909 // we don't contain the mouse anymore. We assume the user is clicking
910 // quicker than the animation and we should close the tab that falls under
912 Tab
* closest_tab
= controller_
->GetTabAt(this, event
.location());
914 controller_
->CloseTab(closest_tab
, CLOSE_TAB_FROM_MOUSE
);
916 } else if (event
.IsOnlyLeftMouseButton() && !event
.IsShiftDown() &&
917 !event
.IsControlDown()) {
918 // If the tab was already selected mouse pressed doesn't change the
919 // selection. Reset it now to handle the case where multiple tabs were
921 controller_
->SelectTab(this);
925 void Tab::OnMouseCaptureLost() {
926 controller_
->EndDrag(END_DRAG_CAPTURE_LOST
);
929 void Tab::OnMouseEntered(const ui::MouseEvent
& event
) {
930 hover_controller_
.Show(views::GlowHoverController::SUBTLE
);
933 void Tab::OnMouseMoved(const ui::MouseEvent
& event
) {
934 hover_controller_
.SetLocation(event
.location());
935 controller_
->OnMouseEventInTab(this, event
);
938 void Tab::OnMouseExited(const ui::MouseEvent
& event
) {
939 hover_controller_
.Hide();
942 void Tab::OnGestureEvent(ui::GestureEvent
* event
) {
943 switch (event
->type()) {
944 case ui::ET_GESTURE_BEGIN
: {
945 if (event
->details().touch_points() != 1)
948 // See comment in OnMousePressed() as to why we copy the event.
949 ui::GestureEvent
event_in_parent(*event
, static_cast<View
*>(this),
951 ui::ListSelectionModel original_selection
;
952 original_selection
.Copy(controller_
->GetSelectionModel());
953 tab_activated_with_last_gesture_begin_
= !IsActive();
955 controller_
->SelectTab(this);
956 gfx::Point
loc(event
->location());
957 views::View::ConvertPointToScreen(this, &loc
);
958 ui::GestureEvent
cloned_event(event_in_parent
, parent(),
959 static_cast<View
*>(this));
960 controller_
->MaybeStartDrag(this, cloned_event
, original_selection
);
964 case ui::ET_GESTURE_END
:
965 controller_
->EndDrag(END_DRAG_COMPLETE
);
968 case ui::ET_GESTURE_SCROLL_UPDATE
:
969 controller_
->ContinueDrag(this, *event
);
978 void Tab::GetAccessibleState(ui::AXViewState
* state
) {
979 state
->role
= ui::AX_ROLE_TAB
;
980 state
->name
= data_
.title
;
983 ////////////////////////////////////////////////////////////////////////////////
986 const gfx::Rect
& Tab::GetTitleBounds() const {
987 return title_bounds_
;
990 const gfx::Rect
& Tab::GetIconBounds() const {
991 return favicon_bounds_
;
994 void Tab::MaybeAdjustLeftForMiniTab(gfx::Rect
* bounds
) const {
995 if (!data().mini
|| width() >= kMiniTabRendererAsNormalTabWidth
)
997 const int mini_delta
= kMiniTabRendererAsNormalTabWidth
- GetMiniWidth();
998 const int ideal_delta
= width() - GetMiniWidth();
999 const int ideal_x
= (GetMiniWidth() - bounds
->width()) / 2;
1000 bounds
->set_x(bounds
->x() + static_cast<int>(
1001 (1 - static_cast<float>(ideal_delta
) / static_cast<float>(mini_delta
)) *
1002 (ideal_x
- bounds
->x())));
1005 void Tab::DataChanged(const TabRendererData
& old
) {
1006 if (data().media_state
!= old
.media_state
|| data().title
!= old
.title
)
1007 TooltipTextChanged();
1009 if (data().blocked
== old
.blocked
)
1018 void Tab::PaintTab(gfx::Canvas
* canvas
) {
1019 // See if the model changes whether the icons should be painted.
1020 const bool show_icon
= ShouldShowIcon();
1021 const bool show_media_indicator
= ShouldShowMediaIndicator();
1022 const bool show_close_button
= ShouldShowCloseBox();
1023 if (show_icon
!= showing_icon_
||
1024 show_media_indicator
!= showing_media_indicator_
||
1025 show_close_button
!= showing_close_button_
) {
1029 PaintTabBackground(canvas
);
1031 SkColor title_color
= GetThemeProvider()->
1032 GetColor(IsSelected() ?
1033 ThemeProperties::COLOR_TAB_TEXT
:
1034 ThemeProperties::COLOR_BACKGROUND_TAB_TEXT
);
1036 if (!data().mini
|| width() > kMiniTabRendererAsNormalTabWidth
)
1037 PaintTitle(canvas
, title_color
);
1042 if (show_media_indicator
)
1043 PaintMediaIndicator(canvas
);
1045 // If the close button color has changed, generate a new one.
1046 if (!close_button_color_
|| title_color
!= close_button_color_
) {
1047 close_button_color_
= title_color
;
1048 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
1049 close_button_
->SetBackground(close_button_color_
,
1050 rb
.GetImageSkiaNamed(IDR_CLOSE_1
),
1051 rb
.GetImageSkiaNamed(IDR_CLOSE_1_MASK
));
1055 void Tab::PaintImmersiveTab(gfx::Canvas
* canvas
) {
1056 // Use transparency for the draw-attention animation.
1058 if (tab_animation_
&&
1059 tab_animation_
->is_animating() &&
1061 alpha
= tab_animation_
->CurrentValueBetween(
1062 255, static_cast<int>(255 * kImmersiveTabMinThrobOpacity
));
1065 // Draw a gray rectangle to represent the tab. This works for mini-tabs as
1066 // well as regular ones. The active tab has a brigher bar.
1068 IsActive() ? kImmersiveActiveTabColor
: kImmersiveInactiveTabColor
;
1069 gfx::Rect bar_rect
= GetImmersiveBarRect();
1070 canvas
->FillRect(bar_rect
, SkColorSetA(color
, alpha
));
1072 // Paint network activity indicator.
1073 // TODO(jamescook): Replace this placeholder animation with a real one.
1074 // For now, let's go with a Cylon eye effect, but in blue.
1075 if (data().network_state
!= TabRendererData::NETWORK_STATE_NONE
) {
1076 const SkColor kEyeColor
= SkColorSetARGB(alpha
, 71, 138, 217);
1077 int eye_width
= bar_rect
.width() / 3;
1078 int eye_offset
= bar_rect
.width() * immersive_loading_step_
/
1079 kImmersiveLoadingStepCount
;
1080 if (eye_offset
+ eye_width
< bar_rect
.width()) {
1081 // Draw a single indicator strip because it fits inside |bar_rect|.
1083 bar_rect
.x() + eye_offset
, 0, eye_width
, kImmersiveBarHeight
);
1084 canvas
->FillRect(eye_rect
, kEyeColor
);
1086 // Draw two indicators to simulate the eye "wrapping around" to the left
1087 // side. The first part fills the remainder of the bar.
1088 int right_eye_width
= bar_rect
.width() - eye_offset
;
1089 gfx::Rect
right_eye_rect(
1090 bar_rect
.x() + eye_offset
, 0, right_eye_width
, kImmersiveBarHeight
);
1091 canvas
->FillRect(right_eye_rect
, kEyeColor
);
1092 // The second part parts the remaining |eye_width| on the left.
1093 int left_eye_width
= eye_offset
+ eye_width
- bar_rect
.width();
1094 gfx::Rect
left_eye_rect(
1095 bar_rect
.x(), 0, left_eye_width
, kImmersiveBarHeight
);
1096 canvas
->FillRect(left_eye_rect
, kEyeColor
);
1101 void Tab::PaintTabBackground(gfx::Canvas
* canvas
) {
1103 PaintActiveTabBackground(canvas
);
1105 if (tab_animation_
.get() &&
1106 tab_animation_
->is_animating() &&
1108 gfx::MultiAnimation
* animation
=
1109 static_cast<gfx::MultiAnimation
*>(tab_animation_
.get());
1110 PaintInactiveTabBackgroundWithTitleChange(canvas
, animation
);
1112 PaintInactiveTabBackground(canvas
);
1115 double throb_value
= GetThrobValue();
1116 if (throb_value
> 0) {
1117 canvas
->SaveLayerAlpha(static_cast<int>(throb_value
* 0xff),
1119 PaintActiveTabBackground(canvas
);
1125 void Tab::PaintInactiveTabBackgroundWithTitleChange(
1126 gfx::Canvas
* canvas
,
1127 gfx::MultiAnimation
* animation
) {
1128 // Render the inactive tab background. We'll use this for clipping.
1129 gfx::Canvas
background_canvas(size(), canvas
->image_scale(), false);
1130 PaintInactiveTabBackground(&background_canvas
);
1132 gfx::ImageSkia
background_image(background_canvas
.ExtractImageRep());
1134 // Draw a radial gradient to hover_canvas.
1135 gfx::Canvas
hover_canvas(size(), canvas
->image_scale(), false);
1136 int radius
= kMiniTitleChangeGradientRadius
;
1137 int x0
= width() + radius
- kMiniTitleChangeInitialXOffset
;
1141 if (animation
->current_part_index() == 0) {
1142 x
= animation
->CurrentValueBetween(x0
, x1
);
1143 } else if (animation
->current_part_index() == 1) {
1146 x
= animation
->CurrentValueBetween(x1
, x2
);
1148 SkPoint center_point
;
1149 center_point
.iset(x
, 0);
1150 SkColor colors
[2] = { kMiniTitleChangeGradientColor1
,
1151 kMiniTitleChangeGradientColor2
};
1152 skia::RefPtr
<SkShader
> shader
= skia::AdoptRef(
1153 SkGradientShader::CreateRadial(
1154 center_point
, SkIntToScalar(radius
), colors
, NULL
, 2,
1155 SkShader::kClamp_TileMode
));
1157 paint
.setShader(shader
.get());
1158 hover_canvas
.DrawRect(gfx::Rect(x
- radius
, -radius
, radius
* 2, radius
* 2),
1161 // Draw the radial gradient clipped to the background into hover_image.
1162 gfx::ImageSkia hover_image
= gfx::ImageSkiaOperations::CreateMaskedImage(
1163 gfx::ImageSkia(hover_canvas
.ExtractImageRep()), background_image
);
1165 // Draw the tab background to the canvas.
1166 canvas
->DrawImageInt(background_image
, 0, 0);
1168 // And then the gradient on top of that.
1169 if (animation
->current_part_index() == 2) {
1170 uint8 alpha
= animation
->CurrentValueBetween(255, 0);
1171 canvas
->DrawImageInt(hover_image
, 0, 0, alpha
);
1173 canvas
->DrawImageInt(hover_image
, 0, 0);
1177 void Tab::PaintInactiveTabBackground(gfx::Canvas
* canvas
) {
1180 views::Widget
* widget
= GetWidget();
1181 GetTabIdAndFrameId(widget
, &tab_id
, &frame_id
);
1183 // Explicitly map the id so we cache correctly.
1184 const chrome::HostDesktopType host_desktop_type
= GetHostDesktopType(this);
1185 tab_id
= chrome::MapThemeImage(host_desktop_type
, tab_id
);
1187 // HasCustomImage() is only true if the theme provides the image. However,
1188 // even if the theme does not provide a tab background, the theme machinery
1189 // will make one if given a frame image.
1190 ui::ThemeProvider
* theme_provider
= GetThemeProvider();
1191 const bool theme_provided_image
= theme_provider
->HasCustomImage(tab_id
) ||
1192 (frame_id
!= 0 && theme_provider
->HasCustomImage(frame_id
));
1194 const bool can_cache
= !theme_provided_image
&&
1195 !hover_controller_
.ShouldDraw();
1198 ui::ScaleFactor scale_factor
=
1199 ui::GetSupportedScaleFactor(canvas
->image_scale());
1200 gfx::ImageSkia
cached_image(GetCachedImage(tab_id
, size(), scale_factor
));
1201 if (cached_image
.width() == 0) {
1202 gfx::Canvas
tmp_canvas(size(), canvas
->image_scale(), false);
1203 PaintInactiveTabBackgroundUsingResourceId(&tmp_canvas
, tab_id
);
1204 cached_image
= gfx::ImageSkia(tmp_canvas
.ExtractImageRep());
1205 SetCachedImage(tab_id
, scale_factor
, cached_image
);
1207 canvas
->DrawImageInt(cached_image
, 0, 0);
1209 PaintInactiveTabBackgroundUsingResourceId(canvas
, tab_id
);
1213 void Tab::PaintInactiveTabBackgroundUsingResourceId(gfx::Canvas
* canvas
,
1215 // WARNING: the inactive tab background may be cached. If you change what it
1216 // is drawn from you may need to update whether it can be cached.
1218 // The tab image needs to be lined up with the background image
1219 // so that it feels partially transparent. These offsets represent the tab
1220 // position within the frame background image.
1221 int offset
= GetMirroredX() + background_offset_
.x();
1223 gfx::ImageSkia
* tab_bg
= GetThemeProvider()->GetImageSkiaNamed(tab_id
);
1225 TabImage
* tab_image
= &tab_active_
;
1226 TabImage
* tab_inactive_image
= &tab_inactive_
;
1227 TabImage
* alpha
= &tab_alpha_
;
1229 // If the theme is providing a custom background image, then its top edge
1230 // should be at the top of the tab. Otherwise, we assume that the background
1231 // image is a composited foreground + frame image.
1232 int bg_offset_y
= GetThemeProvider()->HasCustomImage(tab_id
) ?
1233 0 : background_offset_
.y();
1235 // We need a gfx::Canvas object to be able to extract the image from.
1236 // We draw everything to this canvas and then output it to the canvas
1237 // parameter in addition to using it to mask the hover glow if needed.
1238 gfx::Canvas
background_canvas(size(), canvas
->image_scale(), false);
1240 // Draw left edge. Don't draw over the toolbar, as we're not the foreground
1242 gfx::ImageSkia tab_l
= gfx::ImageSkiaOperations::CreateTiledImage(
1243 *tab_bg
, offset
, bg_offset_y
, tab_image
->l_width
, height());
1244 gfx::ImageSkia theme_l
=
1245 gfx::ImageSkiaOperations::CreateMaskedImage(tab_l
, *alpha
->image_l
);
1246 background_canvas
.DrawImageInt(theme_l
,
1247 0, 0, theme_l
.width(), theme_l
.height() - kToolbarOverlap
,
1248 0, 0, theme_l
.width(), theme_l
.height() - kToolbarOverlap
,
1251 // Draw right edge. Again, don't draw over the toolbar.
1252 gfx::ImageSkia tab_r
= gfx::ImageSkiaOperations::CreateTiledImage(*tab_bg
,
1253 offset
+ width() - tab_image
->r_width
, bg_offset_y
,
1254 tab_image
->r_width
, height());
1255 gfx::ImageSkia theme_r
=
1256 gfx::ImageSkiaOperations::CreateMaskedImage(tab_r
, *alpha
->image_r
);
1257 background_canvas
.DrawImageInt(theme_r
,
1258 0, 0, theme_r
.width(), theme_r
.height() - kToolbarOverlap
,
1259 width() - theme_r
.width(), 0, theme_r
.width(),
1260 theme_r
.height() - kToolbarOverlap
, false);
1262 // Draw center. Instead of masking out the top portion we simply skip over
1263 // it by incrementing by GetDropShadowHeight(), since it's a simple
1264 // rectangle. And again, don't draw over the toolbar.
1265 background_canvas
.TileImageInt(*tab_bg
,
1266 offset
+ tab_image
->l_width
,
1267 bg_offset_y
+ kDropShadowHeight
,
1270 width() - tab_image
->l_width
- tab_image
->r_width
,
1271 height() - kDropShadowHeight
- kToolbarOverlap
);
1273 canvas
->DrawImageInt(
1274 gfx::ImageSkia(background_canvas
.ExtractImageRep()), 0, 0);
1276 if (!GetThemeProvider()->HasCustomImage(tab_id
) &&
1277 hover_controller_
.ShouldDraw()) {
1278 hover_controller_
.Draw(canvas
, gfx::ImageSkia(
1279 background_canvas
.ExtractImageRep()));
1282 // Now draw the highlights/shadows around the tab edge.
1283 canvas
->DrawImageInt(*tab_inactive_image
->image_l
, 0, 0);
1284 canvas
->TileImageInt(*tab_inactive_image
->image_c
,
1285 tab_inactive_image
->l_width
, 0,
1286 width() - tab_inactive_image
->l_width
-
1287 tab_inactive_image
->r_width
,
1289 canvas
->DrawImageInt(*tab_inactive_image
->image_r
,
1290 width() - tab_inactive_image
->r_width
, 0);
1293 void Tab::PaintActiveTabBackground(gfx::Canvas
* canvas
) {
1294 gfx::ImageSkia
* tab_background
=
1295 GetThemeProvider()->GetImageSkiaNamed(IDR_THEME_TOOLBAR
);
1296 int offset
= GetMirroredX() + background_offset_
.x();
1298 TabImage
* tab_image
= &tab_active_
;
1299 TabImage
* alpha
= &tab_alpha_
;
1302 gfx::ImageSkia tab_l
= gfx::ImageSkiaOperations::CreateTiledImage(
1303 *tab_background
, offset
, 0, tab_image
->l_width
, height());
1304 gfx::ImageSkia theme_l
=
1305 gfx::ImageSkiaOperations::CreateMaskedImage(tab_l
, *alpha
->image_l
);
1306 canvas
->DrawImageInt(theme_l
, 0, 0);
1309 gfx::ImageSkia tab_r
= gfx::ImageSkiaOperations::CreateTiledImage(
1311 offset
+ width() - tab_image
->r_width
, 0, tab_image
->r_width
, height());
1312 gfx::ImageSkia theme_r
=
1313 gfx::ImageSkiaOperations::CreateMaskedImage(tab_r
, *alpha
->image_r
);
1314 canvas
->DrawImageInt(theme_r
, width() - tab_image
->r_width
, 0);
1316 // Draw center. Instead of masking out the top portion we simply skip over it
1317 // by incrementing by GetDropShadowHeight(), since it's a simple rectangle.
1318 canvas
->TileImageInt(*tab_background
,
1319 offset
+ tab_image
->l_width
,
1323 width() - tab_image
->l_width
- tab_image
->r_width
,
1324 height() - kDropShadowHeight
);
1326 // Now draw the highlights/shadows around the tab edge.
1327 canvas
->DrawImageInt(*tab_image
->image_l
, 0, 0);
1328 canvas
->TileImageInt(*tab_image
->image_c
, tab_image
->l_width
, 0,
1329 width() - tab_image
->l_width
- tab_image
->r_width
, height());
1330 canvas
->DrawImageInt(*tab_image
->image_r
, width() - tab_image
->r_width
, 0);
1333 void Tab::PaintIcon(gfx::Canvas
* canvas
) {
1334 gfx::Rect bounds
= GetIconBounds();
1335 if (bounds
.IsEmpty())
1338 bounds
.set_x(GetMirroredXForRect(bounds
));
1340 if (data().network_state
!= TabRendererData::NETWORK_STATE_NONE
) {
1341 // Paint network activity (aka throbber) animation frame.
1342 ui::ThemeProvider
* tp
= GetThemeProvider();
1343 gfx::ImageSkia
frames(*tp
->GetImageSkiaNamed(
1344 (data().network_state
== TabRendererData::NETWORK_STATE_WAITING
) ?
1345 IDR_THROBBER_WAITING
: IDR_THROBBER
));
1347 int icon_size
= frames
.height();
1348 int image_offset
= loading_animation_frame_
* icon_size
;
1349 DrawIconCenter(canvas
, frames
, image_offset
,
1350 icon_size
, icon_size
,
1351 bounds
, false, SkPaint());
1352 } else if (should_display_crashed_favicon_
) {
1353 // Paint crash favicon.
1354 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
1355 gfx::ImageSkia
crashed_favicon(*rb
.GetImageSkiaNamed(IDR_SAD_FAVICON
));
1356 bounds
.set_y(bounds
.y() + favicon_hiding_offset_
);
1357 DrawIconCenter(canvas
, crashed_favicon
, 0,
1358 crashed_favicon
.width(),
1359 crashed_favicon
.height(),
1360 bounds
, true, SkPaint());
1361 } else if (!data().favicon
.isNull()) {
1362 // Paint the normal favicon.
1363 DrawIconCenter(canvas
, data().favicon
, 0,
1364 data().favicon
.width(),
1365 data().favicon
.height(),
1366 bounds
, true, SkPaint());
1370 void Tab::PaintMediaIndicator(gfx::Canvas
* canvas
) {
1371 if (media_indicator_bounds_
.IsEmpty() || !media_indicator_animation_
)
1374 gfx::Rect bounds
= media_indicator_bounds_
;
1375 bounds
.set_x(GetMirroredXForRect(bounds
));
1378 paint
.setAntiAlias(true);
1379 double opaqueness
= media_indicator_animation_
->GetCurrentValue();
1380 if (data_
.media_state
== TAB_MEDIA_STATE_NONE
)
1381 opaqueness
= 1.0 - opaqueness
; // Fading out, not in.
1382 paint
.setAlpha(opaqueness
* SK_AlphaOPAQUE
);
1384 const gfx::ImageSkia
& media_indicator_image
=
1385 *(chrome::GetTabMediaIndicatorImage(animating_media_state_
).
1387 DrawIconAtLocation(canvas
, media_indicator_image
, 0,
1388 bounds
.x(), bounds
.y(), media_indicator_image
.width(),
1389 media_indicator_image
.height(), true, paint
);
1392 void Tab::PaintTitle(gfx::Canvas
* canvas
, SkColor title_color
) {
1394 base::string16 title
= data().title
;
1395 if (title
.empty()) {
1396 title
= data().loading
?
1397 l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE
) :
1398 CoreTabHelper::GetDefaultTitle();
1400 Browser::FormatTitleForDisplay(&title
);
1403 canvas
->DrawFadeTruncatingStringRect(title
, gfx::Canvas::TruncateFadeTail
,
1404 gfx::FontList(*font_
), title_color
, GetTitleBounds());
1407 void Tab::AdvanceLoadingAnimation(TabRendererData::NetworkState old_state
,
1408 TabRendererData::NetworkState state
) {
1409 static bool initialized
= false;
1410 static int loading_animation_frame_count
= 0;
1411 static int waiting_animation_frame_count
= 0;
1412 static int waiting_to_loading_frame_count_ratio
= 0;
1415 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
1416 gfx::ImageSkia
loading_animation(*rb
.GetImageSkiaNamed(IDR_THROBBER
));
1417 loading_animation_frame_count
=
1418 loading_animation
.width() / loading_animation
.height();
1419 gfx::ImageSkia
waiting_animation(*rb
.GetImageSkiaNamed(
1420 IDR_THROBBER_WAITING
));
1421 waiting_animation_frame_count
=
1422 waiting_animation
.width() / waiting_animation
.height();
1423 waiting_to_loading_frame_count_ratio
=
1424 waiting_animation_frame_count
/ loading_animation_frame_count
;
1426 base::debug::Alias(&loading_animation_frame_count
);
1427 base::debug::Alias(&waiting_animation_frame_count
);
1428 CHECK_NE(0, waiting_to_loading_frame_count_ratio
) <<
1429 "Number of frames in IDR_THROBBER must be equal to or greater " <<
1430 "than the number of frames in IDR_THROBBER_WAITING. Please " <<
1431 "investigate how this happened and update http://crbug.com/132590, " <<
1432 "this is causing crashes in the wild.";
1435 // The waiting animation is the reverse of the loading animation, but at a
1436 // different rate - the following reverses and scales the animation_frame_
1437 // so that the frame is at an equivalent position when going from one
1438 // animation to the other.
1439 if (state
!= old_state
) {
1440 loading_animation_frame_
= loading_animation_frame_count
-
1441 (loading_animation_frame_
/ waiting_to_loading_frame_count_ratio
);
1444 if (state
== TabRendererData::NETWORK_STATE_WAITING
) {
1445 loading_animation_frame_
= (loading_animation_frame_
+ 1) %
1446 waiting_animation_frame_count
;
1447 // Waiting steps backwards.
1448 immersive_loading_step_
=
1449 (immersive_loading_step_
- 1 + kImmersiveLoadingStepCount
) %
1450 kImmersiveLoadingStepCount
;
1451 } else if (state
== TabRendererData::NETWORK_STATE_LOADING
) {
1452 loading_animation_frame_
= (loading_animation_frame_
+ 1) %
1453 loading_animation_frame_count
;
1454 immersive_loading_step_
= (immersive_loading_step_
+ 1) %
1455 kImmersiveLoadingStepCount
;
1457 loading_animation_frame_
= 0;
1458 immersive_loading_step_
= 0;
1460 if (controller_
->IsImmersiveStyle())
1461 SchedulePaintInRect(GetImmersiveBarRect());
1463 ScheduleIconPaint();
1466 int Tab::IconCapacity() const {
1467 if (height() < GetMinimumUnselectedSize().height())
1469 const int available_width
=
1470 std::max(0, width() - kLeftPadding
- kRightPadding
);
1471 const int width_per_icon
= gfx::kFaviconSize
;
1472 const int kPaddingBetweenIcons
= 2;
1473 if (available_width
>= width_per_icon
&&
1474 available_width
< (width_per_icon
+ kPaddingBetweenIcons
)) {
1477 return available_width
/ (width_per_icon
+ kPaddingBetweenIcons
);
1480 bool Tab::ShouldShowIcon() const {
1481 return chrome::ShouldTabShowFavicon(
1482 IconCapacity(), data().mini
, IsActive(), data().show_icon
,
1483 animating_media_state_
);
1486 bool Tab::ShouldShowMediaIndicator() const {
1487 return chrome::ShouldTabShowMediaIndicator(
1488 IconCapacity(), data().mini
, IsActive(), data().show_icon
,
1489 animating_media_state_
);
1492 bool Tab::ShouldShowCloseBox() const {
1493 return chrome::ShouldTabShowCloseButton(
1494 IconCapacity(), data().mini
, IsActive());
1497 double Tab::GetThrobValue() {
1498 bool is_selected
= IsSelected();
1499 double min
= is_selected
? kSelectedTabOpacity
: 0;
1500 double scale
= is_selected
? kSelectedTabThrobScale
: 1;
1503 if (tab_animation_
.get() && tab_animation_
->is_animating())
1504 return tab_animation_
->GetCurrentValue() * kHoverOpacity
* scale
+ min
;
1507 if (hover_controller_
.ShouldDraw()) {
1508 return kHoverOpacity
* hover_controller_
.GetAnimationValue() * scale
+
1512 return is_selected
? kSelectedTabOpacity
: 0;
1515 void Tab::SetFaviconHidingOffset(int offset
) {
1516 favicon_hiding_offset_
= offset
;
1517 ScheduleIconPaint();
1520 void Tab::DisplayCrashedFavicon() {
1521 should_display_crashed_favicon_
= true;
1524 void Tab::ResetCrashedFavicon() {
1525 should_display_crashed_favicon_
= false;
1528 void Tab::StopCrashAnimation() {
1529 crash_icon_animation_
.reset();
1532 void Tab::StartCrashAnimation() {
1533 crash_icon_animation_
.reset(new FaviconCrashAnimation(this));
1534 crash_icon_animation_
->Start();
1537 bool Tab::IsPerformingCrashAnimation() const {
1538 return crash_icon_animation_
.get() && data_
.IsCrashed();
1541 void Tab::StartMediaIndicatorAnimation() {
1542 media_indicator_animation_
=
1543 chrome::CreateTabMediaIndicatorFadeAnimation(data_
.media_state
);
1544 media_indicator_animation_
->set_delegate(this);
1545 media_indicator_animation_
->Start();
1548 void Tab::ScheduleIconPaint() {
1549 gfx::Rect bounds
= GetIconBounds();
1550 if (bounds
.IsEmpty())
1553 // Extends the area to the bottom when sad_favicon is
1555 if (IsPerformingCrashAnimation())
1556 bounds
.set_height(height() - bounds
.y());
1557 bounds
.set_x(GetMirroredXForRect(bounds
));
1558 SchedulePaintInRect(bounds
);
1561 gfx::Rect
Tab::GetImmersiveBarRect() const {
1562 // The main bar is as wide as the normal tab's horizontal top line.
1563 // This top line of the tab extends a few pixels left and right of the
1564 // center image due to pixels in the rounded corner images.
1565 const int kBarPadding
= 1;
1566 int main_bar_left
= tab_active_
.l_width
- kBarPadding
;
1567 int main_bar_right
= width() - tab_active_
.r_width
+ kBarPadding
;
1569 main_bar_left
, 0, main_bar_right
- main_bar_left
, kImmersiveBarHeight
);
1572 void Tab::GetTabIdAndFrameId(views::Widget
* widget
,
1574 int* frame_id
) const {
1576 widget
->GetTopLevelWidget()->ShouldWindowContentsBeTransparent()) {
1577 *tab_id
= IDR_THEME_TAB_BACKGROUND_V
;
1579 } else if (data().incognito
) {
1580 *tab_id
= IDR_THEME_TAB_BACKGROUND_INCOGNITO
;
1581 *frame_id
= IDR_THEME_FRAME_INCOGNITO
;
1583 *tab_id
= IDR_THEME_TAB_BACKGROUND
;
1584 *frame_id
= IDR_THEME_FRAME
;
1588 ////////////////////////////////////////////////////////////////////////////////
1589 // Tab, private static:
1592 void Tab::InitTabResources() {
1593 static bool initialized
= false;
1599 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
1600 font_
= new gfx::Font(rb
.GetFont(ui::ResourceBundle::BaseFont
));
1601 font_height_
= font_
->GetHeight();
1603 image_cache_
= new ImageCache();
1605 // Load the tab images once now, and maybe again later if the theme changes.
1610 void Tab::LoadTabImages() {
1611 // We're not letting people override tab images just yet.
1612 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
1614 tab_alpha_
.image_l
= rb
.GetImageSkiaNamed(IDR_TAB_ALPHA_LEFT
);
1615 tab_alpha_
.image_r
= rb
.GetImageSkiaNamed(IDR_TAB_ALPHA_RIGHT
);
1617 tab_active_
.image_l
= rb
.GetImageSkiaNamed(IDR_TAB_ACTIVE_LEFT
);
1618 tab_active_
.image_c
= rb
.GetImageSkiaNamed(IDR_TAB_ACTIVE_CENTER
);
1619 tab_active_
.image_r
= rb
.GetImageSkiaNamed(IDR_TAB_ACTIVE_RIGHT
);
1620 tab_active_
.l_width
= tab_active_
.image_l
->width();
1621 tab_active_
.r_width
= tab_active_
.image_r
->width();
1623 tab_inactive_
.image_l
= rb
.GetImageSkiaNamed(IDR_TAB_INACTIVE_LEFT
);
1624 tab_inactive_
.image_c
= rb
.GetImageSkiaNamed(IDR_TAB_INACTIVE_CENTER
);
1625 tab_inactive_
.image_r
= rb
.GetImageSkiaNamed(IDR_TAB_INACTIVE_RIGHT
);
1626 tab_inactive_
.l_width
= tab_inactive_
.image_l
->width();
1627 tab_inactive_
.r_width
= tab_inactive_
.image_r
->width();
1631 gfx::ImageSkia
Tab::GetCachedImage(int resource_id
,
1632 const gfx::Size
& size
,
1633 ui::ScaleFactor scale_factor
) {
1634 for (ImageCache::const_iterator i
= image_cache_
->begin();
1635 i
!= image_cache_
->end(); ++i
) {
1636 if (i
->resource_id
== resource_id
&& i
->scale_factor
== scale_factor
&&
1637 i
->image
.size() == size
) {
1641 return gfx::ImageSkia();
1645 void Tab::SetCachedImage(int resource_id
,
1646 ui::ScaleFactor scale_factor
,
1647 const gfx::ImageSkia
& image
) {
1648 DCHECK_NE(scale_factor
, ui::SCALE_FACTOR_NONE
);
1649 ImageCacheEntry entry
;
1650 entry
.resource_id
= resource_id
;
1651 entry
.scale_factor
= scale_factor
;
1652 entry
.image
= image
;
1653 image_cache_
->push_front(entry
);
1654 if (image_cache_
->size() > kMaxImageCacheSize
)
1655 image_cache_
->pop_back();