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/base/accessibility/accessible_view_state.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/base/layout.h"
30 #include "ui/base/models/list_selection_model.h"
31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/base/theme_provider.h"
33 #include "ui/gfx/animation/animation_container.h"
34 #include "ui/gfx/animation/multi_animation.h"
35 #include "ui/gfx/animation/throb_animation.h"
36 #include "ui/gfx/canvas.h"
37 #include "ui/gfx/color_analysis.h"
38 #include "ui/gfx/favicon_size.h"
39 #include "ui/gfx/font.h"
40 #include "ui/gfx/image/image_skia_operations.h"
41 #include "ui/gfx/path.h"
42 #include "ui/gfx/rect_conversions.h"
43 #include "ui/gfx/skia_util.h"
44 #include "ui/gfx/text_elider.h"
45 #include "ui/views/controls/button/image_button.h"
46 #include "ui/views/widget/tooltip_manager.h"
47 #include "ui/views/widget/widget.h"
48 #include "ui/views/window/non_client_view.h"
51 #include "win8/util/win8_util.h"
55 #include "ui/aura/env.h"
60 // Padding around the "content" of a tab, occupied by the tab border graphics.
63 static int value = -1;
65 switch (ui::GetDisplayLayout()) {
66 case ui::LAYOUT_DESKTOP:
69 case ui::LAYOUT_TOUCH:
80 static int value = -1;
82 switch (ui::GetDisplayLayout()) {
83 case ui::LAYOUT_DESKTOP:
86 case ui::LAYOUT_TOUCH:
97 static int value = -1;
99 switch (ui::GetDisplayLayout()) {
100 case ui::LAYOUT_DESKTOP:
103 case ui::LAYOUT_TOUCH:
113 int bottom_padding() {
114 static int value = -1;
116 switch (ui::GetDisplayLayout()) {
117 case ui::LAYOUT_DESKTOP:
120 case ui::LAYOUT_TOUCH:
130 // Height of the shadow at the top of the tab image assets.
131 int drop_shadow_height() {
132 static int value = -1;
134 switch (ui::GetDisplayLayout()) {
135 case ui::LAYOUT_DESKTOP:
138 case ui::LAYOUT_TOUCH:
148 // Size of icon used for throbber and favicon next to tab title.
149 int tab_icon_size() {
150 static int value = -1;
152 switch (ui::GetDisplayLayout()) {
153 case ui::LAYOUT_DESKTOP:
154 value = gfx::kFaviconSize;
156 case ui::LAYOUT_TOUCH:
166 // How long the pulse throb takes.
167 const int kPulseDurationMs = 200;
169 // Width of touch tabs.
170 static const int kTouchWidth = 120;
172 static const int kToolbarOverlap = 1;
173 static const int kFaviconTitleSpacing = 4;
174 // Additional vertical offset for title text relative to top of tab.
175 // Ash text rendering may be different than Windows.
176 static const int kTitleTextOffsetYAsh = 1;
177 static const int kTitleTextOffsetY = 0;
178 static const int kTitleCloseButtonSpacing = 3;
179 static const int kStandardTitleWidth = 175;
180 // Additional vertical offset for close button relative to top of tab.
181 // Ash needs this to match the text vertical position.
182 static const int kCloseButtonVertFuzzAsh = 1;
183 static const int kCloseButtonVertFuzz = 0;
184 // Additional horizontal offset for close button relative to title text.
185 static const int kCloseButtonHorzFuzz = 3;
187 // When a non-mini-tab becomes a mini-tab the width of the tab animates. If
188 // the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab
189 // is rendered as a normal tab. This is done to avoid having the title
190 // immediately disappear when transitioning a tab from normal to mini-tab.
191 static const int kMiniTabRendererAsNormalTabWidth =
192 browser_defaults::kMiniTabWidth + 30;
194 // How opaque to make the hover state (out of 1).
195 static const double kHoverOpacity = 0.33;
197 // Opacity for non-active selected tabs.
198 static const double kSelectedTabOpacity = .45;
200 // Selected (but not active) tabs have their throb value scaled down by this.
201 static const double kSelectedTabThrobScale = .5;
203 // Durations for the various parts of the mini tab title animation.
204 static const int kMiniTitleChangeAnimationDuration1MS = 1600;
205 static const int kMiniTitleChangeAnimationStart1MS = 0;
206 static const int kMiniTitleChangeAnimationEnd1MS = 1900;
207 static const int kMiniTitleChangeAnimationDuration2MS = 0;
208 static const int kMiniTitleChangeAnimationDuration3MS = 550;
209 static const int kMiniTitleChangeAnimationStart3MS = 150;
210 static const int kMiniTitleChangeAnimationEnd3MS = 800;
211 static const int kMiniTitleChangeAnimationIntervalMS = 40;
213 // Offset from the right edge for the start of the mini title change animation.
214 static const int kMiniTitleChangeInitialXOffset = 6;
216 // Radius of the radial gradient used for mini title change animation.
217 static const int kMiniTitleChangeGradientRadius = 20;
219 // Colors of the gradient used during the mini title change animation.
220 static const SkColor kMiniTitleChangeGradientColor1 = SK_ColorWHITE;
221 static const SkColor kMiniTitleChangeGradientColor2 =
222 SkColorSetARGB(0, 255, 255, 255);
224 // Max number of images to cache. This has to be at least two since rounding
225 // errors may lead to tabs in the same tabstrip having different sizes.
226 const size_t kMaxImageCacheSize = 4;
228 // Height of the miniature tab strip in immersive mode.
229 const int kImmersiveTabHeight = 3;
231 // Height of the small tab indicator rectangles in immersive mode.
232 const int kImmersiveBarHeight = 2;
234 // Color for active and inactive tabs in the immersive mode light strip. These
235 // should be a little brighter than the color of the normal art assets for tabs,
236 // which for active tabs is 230, 230, 230 and for inactive is 184, 184, 184.
237 const SkColor kImmersiveActiveTabColor = SkColorSetRGB(235, 235, 235);
238 const SkColor kImmersiveInactiveTabColor = SkColorSetRGB(190, 190, 190);
240 // The minimum opacity (out of 1) when a tab (either active or inactive) is
241 // throbbing in the immersive mode light strip.
242 const double kImmersiveTabMinThrobOpacity = 0.66;
244 // Number of steps in the immersive mode loading animation.
245 const int kImmersiveLoadingStepCount = 32;
247 const char kTabCloseButtonName[] = "TabCloseButton";
249 void DrawIconAtLocation(gfx::Canvas* canvas,
250 const gfx::ImageSkia& image,
257 const SkPaint& paint) {
258 // NOTE: the clipping is a work around for 69528, it shouldn't be necessary.
260 canvas->ClipRect(gfx::Rect(dst_x, dst_y, icon_width, icon_height));
261 canvas->DrawImageInt(image,
262 image_offset, 0, icon_width, icon_height,
263 dst_x, dst_y, icon_width, icon_height,
268 // Draws the icon image at the center of |bounds|.
269 void DrawIconCenter(gfx::Canvas* canvas,
270 const gfx::ImageSkia& image,
274 const gfx::Rect& bounds,
276 const SkPaint& paint) {
277 // Center the image within bounds.
278 int dst_x = bounds.x() - (icon_width - bounds.width()) / 2;
279 int dst_y = bounds.y() - (icon_height - bounds.height()) / 2;
280 DrawIconAtLocation(canvas, image, image_offset, dst_x, dst_y, icon_width,
281 icon_height, filter, paint);
284 // Draws the icon image at the bottom right corner of |bounds|.
285 void DrawIconBottomRight(gfx::Canvas* canvas,
286 const gfx::ImageSkia& image,
290 const gfx::Rect& bounds,
292 const SkPaint& paint) {
293 int dst_x = bounds.x() + bounds.width() - icon_width;
294 int dst_y = bounds.y() + bounds.height() - icon_height;
295 DrawIconAtLocation(canvas, image, image_offset, dst_x, dst_y, icon_width,
296 icon_height, filter, paint);
299 chrome::HostDesktopType GetHostDesktopType(views::View* view) {
300 // Widget is NULL when tabs are detached.
301 views::Widget* widget = view->GetWidget();
302 return chrome::GetHostDesktopTypeForNativeView(
303 widget ? widget->GetNativeView() : NULL);
308 ////////////////////////////////////////////////////////////////////////////////
309 // FaviconCrashAnimation
311 // A custom animation subclass to manage the favicon crash animation.
312 class Tab::FaviconCrashAnimation : public gfx::LinearAnimation,
313 public gfx::AnimationDelegate {
315 explicit FaviconCrashAnimation(Tab* target)
316 : gfx::LinearAnimation(1000, 25, this),
319 virtual ~FaviconCrashAnimation() {}
321 // gfx::Animation overrides:
322 virtual void AnimateToState(double state) OVERRIDE {
323 const double kHidingOffset = 27;
326 target_->SetFaviconHidingOffset(
327 static_cast<int>(floor(kHidingOffset * 2.0 * state)));
329 target_->DisplayCrashedFavicon();
330 target_->SetFaviconHidingOffset(
332 floor(kHidingOffset - ((state - .5) * 2.0 * kHidingOffset))));
336 // gfx::AnimationDelegate overrides:
337 virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE {
338 target_->SetFaviconHidingOffset(0);
344 DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation);
347 ////////////////////////////////////////////////////////////////////////////////
350 // This is a Button subclass that causes middle clicks to be forwarded to the
351 // parent View by explicitly not handling them in OnMousePressed.
352 class Tab::TabCloseButton : public views::ImageButton {
354 explicit TabCloseButton(Tab* tab) : views::ImageButton(tab), tab_(tab) {}
355 virtual ~TabCloseButton() {}
357 // Overridden from views::View.
358 virtual View* GetEventHandlerForPoint(const gfx::Point& point) OVERRIDE {
359 // Ignore the padding set on the button.
360 gfx::Rect rect = GetContentsBounds();
361 rect.set_x(GetMirroredXForRect(rect));
364 // Include the padding in hit-test for touch events.
365 if (aura::Env::GetInstance()->is_touch_down())
366 rect = GetLocalBounds();
367 #elif defined(OS_WIN)
368 // TODO(sky): Use local-bounds if a touch-point is active.
369 // http://crbug.com/145258
372 return rect.Contains(point) ? this : parent();
375 // Overridden from views::View.
376 virtual View* GetTooltipHandlerForPoint(const gfx::Point& point) OVERRIDE {
377 // Tab close button has no children, so tooltip handler should be the same
378 // as the event handler.
379 // In addition, a hit test has to be performed for the point (as
380 // GetTooltipHandlerForPoint() is responsible for it).
381 if (!HitTestPoint(point))
383 return GetEventHandlerForPoint(point);
386 virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
387 if (tab_->controller())
388 tab_->controller()->OnMouseEventInTab(this, event);
390 bool handled = ImageButton::OnMousePressed(event);
391 // Explicitly mark midle-mouse clicks as non-handled to ensure the tab
393 return event.IsOnlyMiddleMouseButton() ? false : handled;
396 virtual void OnMouseMoved(const ui::MouseEvent& event) OVERRIDE {
397 if (tab_->controller())
398 tab_->controller()->OnMouseEventInTab(this, event);
399 CustomButton::OnMouseMoved(event);
402 virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE {
403 if (tab_->controller())
404 tab_->controller()->OnMouseEventInTab(this, event);
405 CustomButton::OnMouseReleased(event);
408 virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
409 // Consume all gesture events here so that the parent (Tab) does not
410 // start consuming gestures.
411 ImageButton::OnGestureEvent(event);
415 virtual bool HasHitTestMask() const OVERRIDE {
419 virtual void GetHitTestMask(HitTestSource source,
420 gfx::Path* path) const OVERRIDE {
421 // Use the button's contents bounds (which does not include padding)
422 // and the hit test mask of our parent |tab_| to determine if the
423 // button is hidden behind another tab.
425 tab_->GetHitTestMask(source, &tab_mask);
427 gfx::Rect button_bounds(GetContentsBounds());
428 button_bounds.set_x(GetMirroredXForRect(button_bounds));
429 gfx::RectF tab_bounds_f(gfx::SkRectToRectF(tab_mask.getBounds()));
430 views::View::ConvertRectToTarget(tab_, this, &tab_bounds_f);
431 gfx::Rect tab_bounds = gfx::ToEnclosingRect(tab_bounds_f);
433 // If the button is hidden behind another tab, the hit test mask is empty.
434 // Otherwise set the hit test mask to be the contents bounds.
436 if (tab_bounds.Contains(button_bounds)) {
437 // Include the padding in the hit test mask for touch events.
438 if (source == HIT_TEST_SOURCE_TOUCH)
439 button_bounds = GetLocalBounds();
441 path->addRect(RectToSkRect(button_bounds));
445 virtual const char* GetClassName() const OVERRIDE {
446 return kTabCloseButtonName;
452 DISALLOW_COPY_AND_ASSIGN(TabCloseButton);
455 ////////////////////////////////////////////////////////////////////////////////
458 Tab::ImageCacheEntry::ImageCacheEntry()
460 scale_factor(ui::SCALE_FACTOR_NONE) {
463 Tab::ImageCacheEntry::~ImageCacheEntry() {}
465 ////////////////////////////////////////////////////////////////////////////////
469 const char Tab::kViewClassName[] = "Tab";
472 Tab::TabImage Tab::tab_alpha_ = {0};
473 Tab::TabImage Tab::tab_active_ = {0};
474 Tab::TabImage Tab::tab_inactive_ = {0};
476 gfx::Font* Tab::font_ = NULL;
478 int Tab::font_height_ = 0;
480 Tab::ImageCache* Tab::image_cache_ = NULL;
482 ////////////////////////////////////////////////////////////////////////////////
485 Tab::Tab(TabController* controller)
486 : controller_(controller),
489 favicon_hiding_offset_(0),
490 loading_animation_frame_(0),
491 immersive_loading_step_(0),
492 should_display_crashed_favicon_(false),
493 animating_media_state_(TAB_MEDIA_STATE_NONE),
494 theme_provider_(NULL),
495 tab_activated_with_last_gesture_begin_(false),
496 hover_controller_(this),
497 showing_icon_(false),
498 showing_media_indicator_(false),
499 showing_close_button_(false),
500 close_button_color_(0) {
503 // So we get don't get enter/exit on children and don't prematurely stop the
505 set_notify_enter_exit_on_child(true);
509 // Add the Close Button.
510 close_button_ = new TabCloseButton(this);
511 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
512 close_button_->SetImage(views::CustomButton::STATE_NORMAL,
513 rb.GetImageSkiaNamed(IDR_CLOSE_1));
514 close_button_->SetImage(views::CustomButton::STATE_HOVERED,
515 rb.GetImageSkiaNamed(IDR_CLOSE_1_H));
516 close_button_->SetImage(views::CustomButton::STATE_PRESSED,
517 rb.GetImageSkiaNamed(IDR_CLOSE_1_P));
518 close_button_->SetAccessibleName(
519 l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE));
520 // Disable animation so that the red danger sign shows up immediately
521 // to help avoid mis-clicks.
522 close_button_->SetAnimationDuration(0);
523 AddChildView(close_button_);
525 set_context_menu_controller(this);
531 void Tab::set_animation_container(gfx::AnimationContainer* container) {
532 animation_container_ = container;
533 hover_controller_.SetAnimationContainer(container);
536 bool Tab::IsActive() const {
537 return controller() ? controller()->IsActiveTab(this) : true;
540 bool Tab::IsSelected() const {
541 return controller() ? controller()->IsTabSelected(this) : true;
544 void Tab::SetData(const TabRendererData& data) {
545 if (data_.Equals(data))
548 TabRendererData old(data_);
551 if (data_.IsCrashed()) {
552 if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation()) {
553 data_.media_state = TAB_MEDIA_STATE_NONE;
554 #if defined(OS_CHROMEOS)
555 // On Chrome OS, we reload killed tabs automatically when the user
556 // switches to them. Don't display animations for these unless they're
557 // selected (i.e. in the foreground) -- we won't reload these
558 // automatically since we don't want to get into a crash loop.
560 data_.crashed_status != base::TERMINATION_STATUS_PROCESS_WAS_KILLED)
561 StartCrashAnimation();
563 StartCrashAnimation();
567 if (IsPerformingCrashAnimation())
568 StopCrashAnimation();
569 ResetCrashedFavicon();
572 if (data_.media_state != old.media_state) {
573 if (data_.media_state != TAB_MEDIA_STATE_NONE)
574 animating_media_state_ = data_.media_state;
575 StartMediaIndicatorAnimation();
578 if (old.mini != data_.mini) {
579 if (tab_animation_.get() && tab_animation_->is_animating()) {
580 tab_animation_->Stop();
581 tab_animation_.reset(NULL);
591 void Tab::UpdateLoadingAnimation(TabRendererData::NetworkState state) {
592 if (state == data_.network_state &&
593 state == TabRendererData::NETWORK_STATE_NONE) {
594 // If the network state is none and hasn't changed, do nothing. Otherwise we
595 // need to advance the animation frame.
599 TabRendererData::NetworkState old_state = data_.network_state;
600 data_.network_state = state;
601 AdvanceLoadingAnimation(old_state, state);
604 void Tab::StartPulse() {
605 gfx::ThrobAnimation* animation = new gfx::ThrobAnimation(this);
606 animation->SetSlideDuration(kPulseDurationMs);
607 if (animation_container_.get())
608 animation->SetContainer(animation_container_.get());
609 animation->StartThrobbing(std::numeric_limits<int>::max());
610 tab_animation_.reset(animation);
613 void Tab::StopPulse() {
614 if (!tab_animation_.get())
616 tab_animation_->Stop();
617 tab_animation_.reset(NULL);
620 void Tab::StartMiniTabTitleAnimation() {
621 // We can only do this animation if the tab is mini because we will
622 // upcast tab_animation back to MultiAnimation when we draw.
625 if (!tab_animation_.get()) {
626 gfx::MultiAnimation::Parts parts;
628 gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration1MS,
629 gfx::Tween::EASE_OUT));
631 gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration2MS,
634 gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration3MS,
635 gfx::Tween::EASE_IN));
636 parts[0].start_time_ms = kMiniTitleChangeAnimationStart1MS;
637 parts[0].end_time_ms = kMiniTitleChangeAnimationEnd1MS;
638 parts[2].start_time_ms = kMiniTitleChangeAnimationStart3MS;
639 parts[2].end_time_ms = kMiniTitleChangeAnimationEnd3MS;
640 base::TimeDelta timeout =
641 base::TimeDelta::FromMilliseconds(kMiniTitleChangeAnimationIntervalMS);
642 gfx::MultiAnimation* animation = new gfx::MultiAnimation(parts, timeout);
643 if (animation_container_.get())
644 animation->SetContainer(animation_container_.get());
645 animation->set_delegate(this);
646 tab_animation_.reset(animation);
648 tab_animation_->Start();
651 void Tab::StopMiniTabTitleAnimation() {
652 if (!tab_animation_.get())
654 tab_animation_->Stop();
655 tab_animation_.reset(NULL);
659 gfx::Size Tab::GetBasicMinimumUnselectedSize() {
662 gfx::Size minimum_size;
663 minimum_size.set_width(left_padding() + right_padding());
664 // Since we use image images, the real minimum height of the image is
665 // defined most accurately by the height of the end cap images.
666 minimum_size.set_height(tab_active_.image_l->height());
670 gfx::Size Tab::GetMinimumUnselectedSize() {
671 return GetBasicMinimumUnselectedSize();
675 gfx::Size Tab::GetMinimumSelectedSize() {
676 gfx::Size minimum_size = GetBasicMinimumUnselectedSize();
677 minimum_size.set_width(
678 left_padding() + gfx::kFaviconSize + right_padding());
683 gfx::Size Tab::GetStandardSize() {
684 gfx::Size standard_size = GetBasicMinimumUnselectedSize();
685 standard_size.set_width(
686 standard_size.width() + kFaviconTitleSpacing + kStandardTitleWidth);
687 return standard_size;
691 int Tab::GetTouchWidth() {
696 int Tab::GetMiniWidth() {
697 return browser_defaults::kMiniTabWidth;
701 int Tab::GetImmersiveHeight() {
702 return kImmersiveTabHeight;
705 ////////////////////////////////////////////////////////////////////////////////
706 // Tab, AnimationDelegate overrides:
708 void Tab::AnimationProgressed(const gfx::Animation* animation) {
709 // Ignore if the pulse animation is being performed on active tab because
710 // it repaints the same image. See |Tab::PaintTabBackground()|.
711 if (animation == tab_animation_.get() && IsActive())
716 void Tab::AnimationCanceled(const gfx::Animation* animation) {
717 if (media_indicator_animation_ == animation)
718 animating_media_state_ = data_.media_state;
722 void Tab::AnimationEnded(const gfx::Animation* animation) {
723 if (media_indicator_animation_ == animation)
724 animating_media_state_ = data_.media_state;
728 ////////////////////////////////////////////////////////////////////////////////
729 // Tab, views::ButtonListener overrides:
731 void Tab::ButtonPressed(views::Button* sender, const ui::Event& event) {
732 const CloseTabSource source =
733 (event.type() == ui::ET_MOUSE_RELEASED &&
734 (event.flags() & ui::EF_FROM_TOUCH) == 0) ? CLOSE_TAB_FROM_MOUSE :
735 CLOSE_TAB_FROM_TOUCH;
736 DCHECK_EQ(close_button_, sender);
737 controller()->CloseTab(this, source);
738 if (event.type() == ui::ET_GESTURE_TAP)
739 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_TABCLOSE_TAP);
742 ////////////////////////////////////////////////////////////////////////////////
743 // Tab, views::ContextMenuController overrides:
745 void Tab::ShowContextMenuForView(views::View* source,
746 const gfx::Point& point,
747 ui::MenuSourceType source_type) {
748 if (controller() && !closing())
749 controller()->ShowContextMenuForTab(this, point, source_type);
752 ////////////////////////////////////////////////////////////////////////////////
753 // Tab, views::View overrides:
755 void Tab::OnPaint(gfx::Canvas* canvas) {
756 // Don't paint if we're narrower than we can render correctly. (This should
757 // only happen during animations).
758 if (width() < GetMinimumUnselectedSize().width() && !data().mini)
763 if (!controller()->ShouldPaintTab(this, &clip))
765 if (!clip.IsEmpty()) {
767 canvas->ClipRect(clip);
771 if (controller() && controller()->IsImmersiveStyle())
772 PaintImmersiveTab(canvas);
781 gfx::Rect lb = GetContentsBounds();
785 left_padding(), top_padding(), right_padding(), bottom_padding());
787 // The height of the content of the Tab is the largest of the favicon,
788 // the title text and the close button graphic.
789 int content_height = std::max(tab_icon_size(), font_height_);
790 close_button_->set_border(NULL);
791 gfx::Size close_button_size(close_button_->GetPreferredSize());
792 content_height = std::max(content_height, close_button_size.height());
795 showing_icon_ = ShouldShowIcon();
797 // Use the size of the favicon as apps use a bigger favicon size.
798 int favicon_top = top_padding() + content_height / 2 - tab_icon_size() / 2;
799 int favicon_left = lb.x();
800 favicon_bounds_.SetRect(favicon_left, favicon_top,
801 tab_icon_size(), tab_icon_size());
802 MaybeAdjustLeftForMiniTab(&favicon_bounds_);
804 favicon_bounds_.SetRect(lb.x(), lb.y(), 0, 0);
807 // Size the Close button.
808 showing_close_button_ = ShouldShowCloseBox();
809 const bool is_host_desktop_type_ash =
810 GetHostDesktopType(this) == chrome::HOST_DESKTOP_TYPE_ASH;
811 if (showing_close_button_) {
812 const int close_button_vert_fuzz = is_host_desktop_type_ash ?
813 kCloseButtonVertFuzzAsh : kCloseButtonVertFuzz;
814 int close_button_top = top_padding() + close_button_vert_fuzz +
815 (content_height - close_button_size.height()) / 2;
816 // If the ratio of the close button size to tab width exceeds the maximum.
817 // The close button should be as large as possible so that there is a larger
818 // hit-target for touch events. So the close button bounds extends to the
819 // edges of the tab. However, the larger hit-target should be active only
820 // for mouse events, and the close-image should show up in the right place.
821 // So a border is added to the button with necessary padding. The close
822 // button (BaseTab::TabCloseButton) makes sure the padding is a hit-target
823 // only for touch events.
824 int top_border = close_button_top;
825 int bottom_border = height() - (close_button_size.height() + top_border);
826 int left_border = kCloseButtonHorzFuzz;
827 int right_border = width() - (lb.width() + close_button_size.width() +
829 close_button_->set_border(views::Border::CreateEmptyBorder(top_border,
830 left_border, bottom_border, right_border));
831 close_button_->SetPosition(gfx::Point(lb.width(), 0));
832 close_button_->SizeToPreferredSize();
833 close_button_->SetVisible(true);
835 close_button_->SetBounds(0, 0, 0, 0);
836 close_button_->SetVisible(false);
839 showing_media_indicator_ = ShouldShowMediaIndicator();
840 if (showing_media_indicator_) {
841 const gfx::Image& media_indicator_image =
842 chrome::GetTabMediaIndicatorImage(animating_media_state_);
843 media_indicator_bounds_.set_width(media_indicator_image.Width());
844 media_indicator_bounds_.set_height(media_indicator_image.Height());
845 media_indicator_bounds_.set_y(
847 (content_height - media_indicator_bounds_.height()) / 2);
848 const int right = showing_close_button_ ?
849 close_button_->x() + close_button_->GetInsets().left() : lb.right();
850 media_indicator_bounds_.set_x(
851 std::max(lb.x(), right - media_indicator_bounds_.width()));
852 MaybeAdjustLeftForMiniTab(&media_indicator_bounds_);
854 media_indicator_bounds_.SetRect(lb.x(), lb.y(), 0, 0);
857 const int title_text_offset = is_host_desktop_type_ash ?
858 kTitleTextOffsetYAsh : kTitleTextOffsetY;
859 int title_left = favicon_bounds_.right() + kFaviconTitleSpacing;
860 int title_top = top_padding() + title_text_offset +
861 (content_height - font_height_) / 2;
862 // Size the Title text to fill the remaining space.
863 if (!data().mini || width() >= kMiniTabRendererAsNormalTabWidth) {
864 // If the user has big fonts, the title will appear rendered too far down
865 // on the y-axis if we use the regular top padding, so we need to adjust it
866 // so that the text appears centered.
867 gfx::Size minimum_size = GetMinimumUnselectedSize();
868 int text_height = title_top + font_height_ + bottom_padding();
869 if (text_height > minimum_size.height())
870 title_top -= (text_height - minimum_size.height()) / 2;
873 if (showing_media_indicator_) {
874 title_width = media_indicator_bounds_.x() - kTitleCloseButtonSpacing -
876 } else if (close_button_->visible()) {
877 // The close button has an empty border with some padding (see details
878 // above where the close-button's bounds is set). Allow the title to
879 // overlap the empty padding.
880 title_width = close_button_->x() + close_button_->GetInsets().left() -
881 kTitleCloseButtonSpacing - title_left;
883 title_width = lb.width() - title_left;
885 title_width = std::max(title_width, 0);
886 title_bounds_.SetRect(title_left, title_top, title_width, font_height_);
888 title_bounds_.SetRect(title_left, title_top, 0, 0);
891 // Certain UI elements within the Tab (the favicon, etc.) are not represented
892 // as child Views (which is the preferred method). Instead, these UI elements
893 // are drawn directly on the canvas from within Tab::OnPaint(). The Tab's
894 // child Views (for example, the Tab's close button which is a views::Button
895 // instance) are automatically mirrored by the mirroring infrastructure in
896 // views. The elements Tab draws directly on the canvas need to be manually
897 // mirrored if the View's layout is right-to-left.
898 title_bounds_.set_x(GetMirroredXForRect(title_bounds_));
901 void Tab::OnThemeChanged() {
905 const char* Tab::GetClassName() const {
906 return kViewClassName;
909 bool Tab::HasHitTestMask() const {
913 void Tab::GetHitTestMask(HitTestSource source, gfx::Path* path) const {
914 // When the window is maximized we don't want to shave off the edges or top
915 // shadow of the tab, such that the user can click anywhere along the top
916 // edge of the screen to select a tab. Ditto for immersive fullscreen.
917 const views::Widget* widget = GetWidget();
918 bool include_top_shadow =
919 widget && (widget->IsMaximized() || widget->IsFullscreen());
920 TabResources::GetHitTestMask(width(), height(), include_top_shadow, path);
922 // It is possible for a portion of the tab to be occluded if tabs are
923 // stacked, so modify the hit test mask to only include the visible
924 // region of the tab.
927 controller()->ShouldPaintTab(this, &clip);
928 if (clip.size().GetArea()) {
929 SkRect intersection(path->getBounds());
930 intersection.intersect(RectToSkRect(clip));
932 path->addRect(intersection);
937 bool Tab::GetTooltipText(const gfx::Point& p, string16* tooltip) const {
938 // TODO(miu): Rectify inconsistent tooltip behavior. http://crbug.com/310947
940 if (data_.media_state != TAB_MEDIA_STATE_NONE) {
941 *tooltip = chrome::AssembleTabTooltipText(data_.title, data_.media_state);
945 if (data_.title.empty())
948 // Only show the tooltip if the title is truncated.
949 if (font_->GetStringWidth(data_.title) > GetTitleBounds().width()) {
950 *tooltip = data_.title;
956 bool Tab::GetTooltipTextOrigin(const gfx::Point& p, gfx::Point* origin) const {
957 origin->set_x(title_bounds_.x() + 10);
958 origin->set_y(-views::TooltipManager::GetTooltipHeight() - 4);
962 ui::ThemeProvider* Tab::GetThemeProvider() const {
963 ui::ThemeProvider* tp = View::GetThemeProvider();
964 return tp ? tp : theme_provider_;
967 bool Tab::OnMousePressed(const ui::MouseEvent& event) {
971 controller()->OnMouseEventInTab(this, event);
973 // Allow a right click from touch to drag, which corresponds to a long click.
974 if (event.IsOnlyLeftMouseButton() ||
975 (event.IsOnlyRightMouseButton() && event.flags() & ui::EF_FROM_TOUCH)) {
976 ui::ListSelectionModel original_selection;
977 original_selection.Copy(controller()->GetSelectionModel());
978 // Changing the selection may cause our bounds to change. If that happens
979 // the location of the event may no longer be valid. Create a copy of the
980 // event in the parents coordinate, which won't change, and recreate an
981 // event after changing so the coordinates are correct.
982 ui::MouseEvent event_in_parent(event, static_cast<View*>(this), parent());
983 if (controller()->SupportsMultipleSelection()) {
984 if (event.IsShiftDown() && event.IsControlDown()) {
985 controller()->AddSelectionFromAnchorTo(this);
986 } else if (event.IsShiftDown()) {
987 controller()->ExtendSelectionTo(this);
988 } else if (event.IsControlDown()) {
989 controller()->ToggleSelected(this);
991 // Don't allow dragging non-selected tabs.
994 } else if (!IsSelected()) {
995 controller()->SelectTab(this);
997 } else if (!IsSelected()) {
998 controller()->SelectTab(this);
1000 ui::MouseEvent cloned_event(event_in_parent, parent(),
1001 static_cast<View*>(this));
1002 controller()->MaybeStartDrag(this, cloned_event, original_selection);
1007 bool Tab::OnMouseDragged(const ui::MouseEvent& event) {
1009 controller()->ContinueDrag(this, event);
1013 void Tab::OnMouseReleased(const ui::MouseEvent& event) {
1017 controller()->OnMouseEventInTab(this, event);
1019 // Notify the drag helper that we're done with any potential drag operations.
1020 // Clean up the drag helper, which is re-created on the next mouse press.
1021 // In some cases, ending the drag will schedule the tab for destruction; if
1022 // so, bail immediately, since our members are already dead and we shouldn't
1023 // do anything else except drop the tab where it is.
1024 if (controller()->EndDrag(END_DRAG_COMPLETE))
1027 // Close tab on middle click, but only if the button is released over the tab
1028 // (normal windows behavior is to discard presses of a UI element where the
1029 // releases happen off the element).
1030 if (event.IsMiddleMouseButton()) {
1031 if (HitTestPoint(event.location())) {
1032 controller()->CloseTab(this, CLOSE_TAB_FROM_MOUSE);
1033 } else if (closing_) {
1034 // We're animating closed and a middle mouse button was pushed on us but
1035 // we don't contain the mouse anymore. We assume the user is clicking
1036 // quicker than the animation and we should close the tab that falls under
1038 Tab* closest_tab = controller()->GetTabAt(this, event.location());
1040 controller()->CloseTab(closest_tab, CLOSE_TAB_FROM_MOUSE);
1042 } else if (event.IsOnlyLeftMouseButton() && !event.IsShiftDown() &&
1043 !event.IsControlDown()) {
1044 // If the tab was already selected mouse pressed doesn't change the
1045 // selection. Reset it now to handle the case where multiple tabs were
1047 controller()->SelectTab(this);
1051 void Tab::OnMouseCaptureLost() {
1053 controller()->EndDrag(END_DRAG_CAPTURE_LOST);
1056 void Tab::OnMouseEntered(const ui::MouseEvent& event) {
1057 hover_controller_.Show(views::GlowHoverController::SUBTLE);
1060 void Tab::OnMouseMoved(const ui::MouseEvent& event) {
1061 hover_controller_.SetLocation(event.location());
1063 controller()->OnMouseEventInTab(this, event);
1066 void Tab::OnMouseExited(const ui::MouseEvent& event) {
1067 hover_controller_.Hide();
1070 void Tab::OnGestureEvent(ui::GestureEvent* event) {
1071 if (!controller()) {
1072 event->SetHandled();
1076 switch (event->type()) {
1077 case ui::ET_GESTURE_BEGIN: {
1078 if (event->details().touch_points() != 1)
1081 // See comment in OnMousePressed() as to why we copy the event.
1082 ui::GestureEvent event_in_parent(*event, static_cast<View*>(this),
1084 ui::ListSelectionModel original_selection;
1085 original_selection.Copy(controller()->GetSelectionModel());
1086 tab_activated_with_last_gesture_begin_ = !IsActive();
1088 controller()->SelectTab(this);
1089 gfx::Point loc(event->location());
1090 views::View::ConvertPointToScreen(this, &loc);
1091 ui::GestureEvent cloned_event(event_in_parent, parent(),
1092 static_cast<View*>(this));
1093 controller()->MaybeStartDrag(this, cloned_event, original_selection);
1097 case ui::ET_GESTURE_END:
1098 controller()->EndDrag(END_DRAG_COMPLETE);
1101 case ui::ET_GESTURE_SCROLL_UPDATE:
1102 controller()->ContinueDrag(this, *event);
1108 event->SetHandled();
1111 void Tab::GetAccessibleState(ui::AccessibleViewState* state) {
1112 state->role = ui::AccessibilityTypes::ROLE_PAGETAB;
1113 state->name = data_.title;
1116 ////////////////////////////////////////////////////////////////////////////////
1119 const gfx::Rect& Tab::GetTitleBounds() const {
1120 return title_bounds_;
1123 const gfx::Rect& Tab::GetIconBounds() const {
1124 return favicon_bounds_;
1127 void Tab::MaybeAdjustLeftForMiniTab(gfx::Rect* bounds) const {
1128 if (!data().mini || width() >= kMiniTabRendererAsNormalTabWidth)
1130 const int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth();
1131 const int ideal_delta = width() - GetMiniWidth();
1132 const int ideal_x = (GetMiniWidth() - bounds->width()) / 2;
1133 bounds->set_x(bounds->x() + static_cast<int>(
1134 (1 - static_cast<float>(ideal_delta) / static_cast<float>(mini_delta)) *
1135 (ideal_x - bounds->x())));
1138 void Tab::DataChanged(const TabRendererData& old) {
1139 if (data().blocked == old.blocked)
1148 void Tab::PaintTab(gfx::Canvas* canvas) {
1149 // See if the model changes whether the icons should be painted.
1150 const bool show_icon = ShouldShowIcon();
1151 const bool show_media_indicator = ShouldShowMediaIndicator();
1152 const bool show_close_button = ShouldShowCloseBox();
1153 if (show_icon != showing_icon_ ||
1154 show_media_indicator != showing_media_indicator_ ||
1155 show_close_button != showing_close_button_) {
1159 PaintTabBackground(canvas);
1161 SkColor title_color = GetThemeProvider()->
1162 GetColor(IsSelected() ?
1163 ThemeProperties::COLOR_TAB_TEXT :
1164 ThemeProperties::COLOR_BACKGROUND_TAB_TEXT);
1166 if (!data().mini || width() > kMiniTabRendererAsNormalTabWidth)
1167 PaintTitle(canvas, title_color);
1172 if (show_media_indicator)
1173 PaintMediaIndicator(canvas);
1175 // If the close button color has changed, generate a new one.
1176 if (!close_button_color_ || title_color != close_button_color_) {
1177 close_button_color_ = title_color;
1178 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1179 close_button_->SetBackground(close_button_color_,
1180 rb.GetImageSkiaNamed(IDR_CLOSE_1),
1181 rb.GetImageSkiaNamed(IDR_CLOSE_1_MASK));
1185 void Tab::PaintImmersiveTab(gfx::Canvas* canvas) {
1186 // Use transparency for the draw-attention animation.
1188 if (tab_animation_ &&
1189 tab_animation_->is_animating() &&
1191 alpha = tab_animation_->CurrentValueBetween(
1192 255, static_cast<int>(255 * kImmersiveTabMinThrobOpacity));
1195 // Draw a gray rectangle to represent the tab. This works for mini-tabs as
1196 // well as regular ones. The active tab has a brigher bar.
1198 IsActive() ? kImmersiveActiveTabColor : kImmersiveInactiveTabColor;
1199 gfx::Rect bar_rect = GetImmersiveBarRect();
1200 canvas->FillRect(bar_rect, SkColorSetA(color, alpha));
1202 // Paint network activity indicator.
1203 // TODO(jamescook): Replace this placeholder animation with a real one.
1204 // For now, let's go with a Cylon eye effect, but in blue.
1205 if (data().network_state != TabRendererData::NETWORK_STATE_NONE) {
1206 const SkColor kEyeColor = SkColorSetARGB(alpha, 71, 138, 217);
1207 int eye_width = bar_rect.width() / 3;
1208 int eye_offset = bar_rect.width() * immersive_loading_step_ /
1209 kImmersiveLoadingStepCount;
1210 if (eye_offset + eye_width < bar_rect.width()) {
1211 // Draw a single indicator strip because it fits inside |bar_rect|.
1213 bar_rect.x() + eye_offset, 0, eye_width, kImmersiveBarHeight);
1214 canvas->FillRect(eye_rect, kEyeColor);
1216 // Draw two indicators to simulate the eye "wrapping around" to the left
1217 // side. The first part fills the remainder of the bar.
1218 int right_eye_width = bar_rect.width() - eye_offset;
1219 gfx::Rect right_eye_rect(
1220 bar_rect.x() + eye_offset, 0, right_eye_width, kImmersiveBarHeight);
1221 canvas->FillRect(right_eye_rect, kEyeColor);
1222 // The second part parts the remaining |eye_width| on the left.
1223 int left_eye_width = eye_offset + eye_width - bar_rect.width();
1224 gfx::Rect left_eye_rect(
1225 bar_rect.x(), 0, left_eye_width, kImmersiveBarHeight);
1226 canvas->FillRect(left_eye_rect, kEyeColor);
1231 void Tab::PaintTabBackground(gfx::Canvas* canvas) {
1233 PaintActiveTabBackground(canvas);
1235 if (tab_animation_.get() &&
1236 tab_animation_->is_animating() &&
1238 gfx::MultiAnimation* animation =
1239 static_cast<gfx::MultiAnimation*>(tab_animation_.get());
1240 PaintInactiveTabBackgroundWithTitleChange(canvas, animation);
1242 PaintInactiveTabBackground(canvas);
1245 double throb_value = GetThrobValue();
1246 if (throb_value > 0) {
1247 canvas->SaveLayerAlpha(static_cast<int>(throb_value * 0xff),
1249 PaintActiveTabBackground(canvas);
1255 void Tab::PaintInactiveTabBackgroundWithTitleChange(
1256 gfx::Canvas* canvas,
1257 gfx::MultiAnimation* animation) {
1258 // Render the inactive tab background. We'll use this for clipping.
1259 gfx::Canvas background_canvas(size(), canvas->image_scale(), false);
1260 PaintInactiveTabBackground(&background_canvas);
1262 gfx::ImageSkia background_image(background_canvas.ExtractImageRep());
1264 // Draw a radial gradient to hover_canvas.
1265 gfx::Canvas hover_canvas(size(), canvas->image_scale(), false);
1266 int radius = kMiniTitleChangeGradientRadius;
1267 int x0 = width() + radius - kMiniTitleChangeInitialXOffset;
1271 if (animation->current_part_index() == 0) {
1272 x = animation->CurrentValueBetween(x0, x1);
1273 } else if (animation->current_part_index() == 1) {
1276 x = animation->CurrentValueBetween(x1, x2);
1278 SkPoint center_point;
1279 center_point.iset(x, 0);
1280 SkColor colors[2] = { kMiniTitleChangeGradientColor1,
1281 kMiniTitleChangeGradientColor2 };
1282 skia::RefPtr<SkShader> shader = skia::AdoptRef(
1283 SkGradientShader::CreateRadial(
1284 center_point, SkIntToScalar(radius), colors, NULL, 2,
1285 SkShader::kClamp_TileMode));
1287 paint.setShader(shader.get());
1288 hover_canvas.DrawRect(gfx::Rect(x - radius, -radius, radius * 2, radius * 2),
1291 // Draw the radial gradient clipped to the background into hover_image.
1292 gfx::ImageSkia hover_image = gfx::ImageSkiaOperations::CreateMaskedImage(
1293 gfx::ImageSkia(hover_canvas.ExtractImageRep()), background_image);
1295 // Draw the tab background to the canvas.
1296 canvas->DrawImageInt(background_image, 0, 0);
1298 // And then the gradient on top of that.
1299 if (animation->current_part_index() == 2) {
1300 uint8 alpha = animation->CurrentValueBetween(255, 0);
1301 canvas->DrawImageInt(hover_image, 0, 0, alpha);
1303 canvas->DrawImageInt(hover_image, 0, 0);
1307 void Tab::PaintInactiveTabBackground(gfx::Canvas* canvas) {
1310 views::Widget* widget = GetWidget();
1311 GetTabIdAndFrameId(widget, &tab_id, &frame_id);
1313 // Explicitly map the id so we cache correctly.
1314 const chrome::HostDesktopType host_desktop_type = GetHostDesktopType(this);
1315 tab_id = chrome::MapThemeImage(host_desktop_type, tab_id);
1317 // HasCustomImage() is only true if the theme provides the image. However,
1318 // even if the theme does not provide a tab background, the theme machinery
1319 // will make one if given a frame image.
1320 ui::ThemeProvider* theme_provider = GetThemeProvider();
1321 const bool theme_provided_image = theme_provider->HasCustomImage(tab_id) ||
1322 (frame_id != 0 && theme_provider->HasCustomImage(frame_id));
1324 const bool can_cache = !theme_provided_image &&
1325 !hover_controller_.ShouldDraw();
1328 ui::ScaleFactor scale_factor =
1329 ui::GetSupportedScaleFactor(canvas->image_scale());
1330 gfx::ImageSkia cached_image(GetCachedImage(tab_id, size(), scale_factor));
1331 if (cached_image.width() == 0) {
1332 gfx::Canvas tmp_canvas(size(), canvas->image_scale(), false);
1333 PaintInactiveTabBackgroundUsingResourceId(&tmp_canvas, tab_id);
1334 cached_image = gfx::ImageSkia(tmp_canvas.ExtractImageRep());
1335 SetCachedImage(tab_id, scale_factor, cached_image);
1337 canvas->DrawImageInt(cached_image, 0, 0);
1339 PaintInactiveTabBackgroundUsingResourceId(canvas, tab_id);
1343 void Tab::PaintInactiveTabBackgroundUsingResourceId(gfx::Canvas* canvas,
1345 // WARNING: the inactive tab background may be cached. If you change what it
1346 // is drawn from you may need to update whether it can be cached.
1348 // The tab image needs to be lined up with the background image
1349 // so that it feels partially transparent. These offsets represent the tab
1350 // position within the frame background image.
1351 int offset = GetMirroredX() + background_offset_.x();
1353 gfx::ImageSkia* tab_bg = GetThemeProvider()->GetImageSkiaNamed(tab_id);
1355 TabImage* tab_image = &tab_active_;
1356 TabImage* tab_inactive_image = &tab_inactive_;
1357 TabImage* alpha = &tab_alpha_;
1359 // If the theme is providing a custom background image, then its top edge
1360 // should be at the top of the tab. Otherwise, we assume that the background
1361 // image is a composited foreground + frame image.
1362 int bg_offset_y = GetThemeProvider()->HasCustomImage(tab_id) ?
1363 0 : background_offset_.y();
1365 // We need a gfx::Canvas object to be able to extract the image from.
1366 // We draw everything to this canvas and then output it to the canvas
1367 // parameter in addition to using it to mask the hover glow if needed.
1368 gfx::Canvas background_canvas(size(), canvas->image_scale(), false);
1370 // Draw left edge. Don't draw over the toolbar, as we're not the foreground
1372 gfx::ImageSkia tab_l = gfx::ImageSkiaOperations::CreateTiledImage(
1373 *tab_bg, offset, bg_offset_y, tab_image->l_width, height());
1374 gfx::ImageSkia theme_l =
1375 gfx::ImageSkiaOperations::CreateMaskedImage(tab_l, *alpha->image_l);
1376 background_canvas.DrawImageInt(theme_l,
1377 0, 0, theme_l.width(), theme_l.height() - kToolbarOverlap,
1378 0, 0, theme_l.width(), theme_l.height() - kToolbarOverlap,
1381 // Draw right edge. Again, don't draw over the toolbar.
1382 gfx::ImageSkia tab_r = gfx::ImageSkiaOperations::CreateTiledImage(*tab_bg,
1383 offset + width() - tab_image->r_width, bg_offset_y,
1384 tab_image->r_width, height());
1385 gfx::ImageSkia theme_r =
1386 gfx::ImageSkiaOperations::CreateMaskedImage(tab_r, *alpha->image_r);
1387 background_canvas.DrawImageInt(theme_r,
1388 0, 0, theme_r.width(), theme_r.height() - kToolbarOverlap,
1389 width() - theme_r.width(), 0, theme_r.width(),
1390 theme_r.height() - kToolbarOverlap, false);
1392 // Draw center. Instead of masking out the top portion we simply skip over
1393 // it by incrementing by GetDropShadowHeight(), since it's a simple
1394 // rectangle. And again, don't draw over the toolbar.
1395 background_canvas.TileImageInt(*tab_bg,
1396 offset + tab_image->l_width,
1397 bg_offset_y + drop_shadow_height(),
1399 drop_shadow_height(),
1400 width() - tab_image->l_width - tab_image->r_width,
1401 height() - drop_shadow_height() - kToolbarOverlap);
1403 canvas->DrawImageInt(
1404 gfx::ImageSkia(background_canvas.ExtractImageRep()), 0, 0);
1406 if (!GetThemeProvider()->HasCustomImage(tab_id) &&
1407 hover_controller_.ShouldDraw()) {
1408 hover_controller_.Draw(canvas, gfx::ImageSkia(
1409 background_canvas.ExtractImageRep()));
1412 // Now draw the highlights/shadows around the tab edge.
1413 canvas->DrawImageInt(*tab_inactive_image->image_l, 0, 0);
1414 canvas->TileImageInt(*tab_inactive_image->image_c,
1415 tab_inactive_image->l_width, 0,
1416 width() - tab_inactive_image->l_width -
1417 tab_inactive_image->r_width,
1419 canvas->DrawImageInt(*tab_inactive_image->image_r,
1420 width() - tab_inactive_image->r_width, 0);
1423 void Tab::PaintActiveTabBackground(gfx::Canvas* canvas) {
1424 gfx::ImageSkia* tab_background =
1425 GetThemeProvider()->GetImageSkiaNamed(IDR_THEME_TOOLBAR);
1426 int offset = GetMirroredX() + background_offset_.x();
1428 TabImage* tab_image = &tab_active_;
1429 TabImage* alpha = &tab_alpha_;
1432 gfx::ImageSkia tab_l = gfx::ImageSkiaOperations::CreateTiledImage(
1433 *tab_background, offset, 0, tab_image->l_width, height());
1434 gfx::ImageSkia theme_l =
1435 gfx::ImageSkiaOperations::CreateMaskedImage(tab_l, *alpha->image_l);
1436 canvas->DrawImageInt(theme_l, 0, 0);
1439 gfx::ImageSkia tab_r = gfx::ImageSkiaOperations::CreateTiledImage(
1441 offset + width() - tab_image->r_width, 0, tab_image->r_width, height());
1442 gfx::ImageSkia theme_r =
1443 gfx::ImageSkiaOperations::CreateMaskedImage(tab_r, *alpha->image_r);
1444 canvas->DrawImageInt(theme_r, width() - tab_image->r_width, 0);
1446 // Draw center. Instead of masking out the top portion we simply skip over it
1447 // by incrementing by GetDropShadowHeight(), since it's a simple rectangle.
1448 canvas->TileImageInt(*tab_background,
1449 offset + tab_image->l_width,
1450 drop_shadow_height(),
1452 drop_shadow_height(),
1453 width() - tab_image->l_width - tab_image->r_width,
1454 height() - drop_shadow_height());
1456 // Now draw the highlights/shadows around the tab edge.
1457 canvas->DrawImageInt(*tab_image->image_l, 0, 0);
1458 canvas->TileImageInt(*tab_image->image_c, tab_image->l_width, 0,
1459 width() - tab_image->l_width - tab_image->r_width, height());
1460 canvas->DrawImageInt(*tab_image->image_r, width() - tab_image->r_width, 0);
1463 void Tab::PaintIcon(gfx::Canvas* canvas) {
1464 gfx::Rect bounds = GetIconBounds();
1465 if (bounds.IsEmpty())
1468 bounds.set_x(GetMirroredXForRect(bounds));
1470 if (data().network_state != TabRendererData::NETWORK_STATE_NONE) {
1471 // Paint network activity (aka throbber) animation frame.
1472 ui::ThemeProvider* tp = GetThemeProvider();
1473 gfx::ImageSkia frames(*tp->GetImageSkiaNamed(
1474 (data().network_state == TabRendererData::NETWORK_STATE_WAITING) ?
1475 IDR_THROBBER_WAITING : IDR_THROBBER));
1477 int icon_size = frames.height();
1478 int image_offset = loading_animation_frame_ * icon_size;
1479 DrawIconCenter(canvas, frames, image_offset,
1480 icon_size, icon_size,
1481 bounds, false, SkPaint());
1482 } else if (should_display_crashed_favicon_) {
1483 // Paint crash favicon.
1484 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1485 gfx::ImageSkia crashed_favicon(*rb.GetImageSkiaNamed(IDR_SAD_FAVICON));
1486 bounds.set_y(bounds.y() + favicon_hiding_offset_);
1487 DrawIconCenter(canvas, crashed_favicon, 0,
1488 crashed_favicon.width(),
1489 crashed_favicon.height(),
1490 bounds, true, SkPaint());
1491 } else if (!data().favicon.isNull()) {
1492 // Paint the normal favicon.
1493 DrawIconCenter(canvas, data().favicon, 0,
1494 data().favicon.width(),
1495 data().favicon.height(),
1496 bounds, true, SkPaint());
1500 void Tab::PaintMediaIndicator(gfx::Canvas* canvas) {
1501 if (media_indicator_bounds_.IsEmpty() || !media_indicator_animation_)
1504 gfx::Rect bounds = media_indicator_bounds_;
1505 bounds.set_x(GetMirroredXForRect(bounds));
1508 paint.setAntiAlias(true);
1509 double opaqueness = media_indicator_animation_->GetCurrentValue();
1510 if (data_.media_state == TAB_MEDIA_STATE_NONE)
1511 opaqueness = 1.0 - opaqueness; // Fading out, not in.
1512 paint.setAlpha(opaqueness * SK_AlphaOPAQUE);
1514 const gfx::ImageSkia& media_indicator_image =
1515 *(chrome::GetTabMediaIndicatorImage(animating_media_state_).
1517 DrawIconAtLocation(canvas, media_indicator_image, 0,
1518 bounds.x(), bounds.y(), media_indicator_image.width(),
1519 media_indicator_image.height(), true, paint);
1522 void Tab::PaintTitle(gfx::Canvas* canvas, SkColor title_color) {
1524 string16 title = data().title;
1525 if (title.empty()) {
1526 title = data().loading ?
1527 l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) :
1528 CoreTabHelper::GetDefaultTitle();
1530 Browser::FormatTitleForDisplay(&title);
1533 canvas->DrawFadeTruncatingStringRect(title, gfx::Canvas::TruncateFadeTail,
1534 gfx::FontList(*font_), title_color, GetTitleBounds());
1537 void Tab::AdvanceLoadingAnimation(TabRendererData::NetworkState old_state,
1538 TabRendererData::NetworkState state) {
1539 static bool initialized = false;
1540 static int loading_animation_frame_count = 0;
1541 static int waiting_animation_frame_count = 0;
1542 static int waiting_to_loading_frame_count_ratio = 0;
1545 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1546 gfx::ImageSkia loading_animation(*rb.GetImageSkiaNamed(IDR_THROBBER));
1547 loading_animation_frame_count =
1548 loading_animation.width() / loading_animation.height();
1549 gfx::ImageSkia waiting_animation(*rb.GetImageSkiaNamed(
1550 IDR_THROBBER_WAITING));
1551 waiting_animation_frame_count =
1552 waiting_animation.width() / waiting_animation.height();
1553 waiting_to_loading_frame_count_ratio =
1554 waiting_animation_frame_count / loading_animation_frame_count;
1556 base::debug::Alias(&loading_animation_frame_count);
1557 base::debug::Alias(&waiting_animation_frame_count);
1558 CHECK_NE(0, waiting_to_loading_frame_count_ratio) <<
1559 "Number of frames in IDR_THROBBER must be equal to or greater " <<
1560 "than the number of frames in IDR_THROBBER_WAITING. Please " <<
1561 "investigate how this happened and update http://crbug.com/132590, " <<
1562 "this is causing crashes in the wild.";
1565 // The waiting animation is the reverse of the loading animation, but at a
1566 // different rate - the following reverses and scales the animation_frame_
1567 // so that the frame is at an equivalent position when going from one
1568 // animation to the other.
1569 if (state != old_state) {
1570 loading_animation_frame_ = loading_animation_frame_count -
1571 (loading_animation_frame_ / waiting_to_loading_frame_count_ratio);
1574 if (state == TabRendererData::NETWORK_STATE_WAITING) {
1575 loading_animation_frame_ = (loading_animation_frame_ + 1) %
1576 waiting_animation_frame_count;
1577 // Waiting steps backwards.
1578 immersive_loading_step_ =
1579 (immersive_loading_step_ - 1 + kImmersiveLoadingStepCount) %
1580 kImmersiveLoadingStepCount;
1581 } else if (state == TabRendererData::NETWORK_STATE_LOADING) {
1582 loading_animation_frame_ = (loading_animation_frame_ + 1) %
1583 loading_animation_frame_count;
1584 immersive_loading_step_ = (immersive_loading_step_ + 1) %
1585 kImmersiveLoadingStepCount;
1587 loading_animation_frame_ = 0;
1588 immersive_loading_step_ = 0;
1590 if (controller() && controller()->IsImmersiveStyle())
1591 SchedulePaintInRect(GetImmersiveBarRect());
1593 ScheduleIconPaint();
1596 int Tab::IconCapacity() const {
1597 if (height() < GetMinimumUnselectedSize().height())
1599 const int available_width =
1600 std::max(0, width() - left_padding() - right_padding());
1601 const int width_per_icon = tab_icon_size();
1602 const int kPaddingBetweenIcons = 2;
1603 if (available_width >= width_per_icon &&
1604 available_width < (width_per_icon + kPaddingBetweenIcons)) {
1607 return available_width / (width_per_icon + kPaddingBetweenIcons);
1610 bool Tab::ShouldShowIcon() const {
1611 return chrome::ShouldTabShowFavicon(
1612 IconCapacity(), data().mini, IsActive(), data().show_icon,
1613 animating_media_state_);
1616 bool Tab::ShouldShowMediaIndicator() const {
1617 return chrome::ShouldTabShowMediaIndicator(
1618 IconCapacity(), data().mini, IsActive(), data().show_icon,
1619 animating_media_state_);
1622 bool Tab::ShouldShowCloseBox() const {
1623 return chrome::ShouldTabShowCloseButton(
1624 IconCapacity(), data().mini, IsActive());
1627 double Tab::GetThrobValue() {
1628 bool is_selected = IsSelected();
1629 double min = is_selected ? kSelectedTabOpacity : 0;
1630 double scale = is_selected ? kSelectedTabThrobScale : 1;
1633 if (tab_animation_.get() && tab_animation_->is_animating())
1634 return tab_animation_->GetCurrentValue() * kHoverOpacity * scale + min;
1637 if (hover_controller_.ShouldDraw()) {
1638 return kHoverOpacity * hover_controller_.GetAnimationValue() * scale +
1642 return is_selected ? kSelectedTabOpacity : 0;
1645 void Tab::SetFaviconHidingOffset(int offset) {
1646 favicon_hiding_offset_ = offset;
1647 ScheduleIconPaint();
1650 void Tab::DisplayCrashedFavicon() {
1651 should_display_crashed_favicon_ = true;
1654 void Tab::ResetCrashedFavicon() {
1655 should_display_crashed_favicon_ = false;
1658 void Tab::StopCrashAnimation() {
1659 crash_icon_animation_.reset();
1662 void Tab::StartCrashAnimation() {
1663 crash_icon_animation_.reset(new FaviconCrashAnimation(this));
1664 crash_icon_animation_->Start();
1667 bool Tab::IsPerformingCrashAnimation() const {
1668 return crash_icon_animation_.get() && data_.IsCrashed();
1671 void Tab::StartMediaIndicatorAnimation() {
1672 media_indicator_animation_ =
1673 chrome::CreateTabMediaIndicatorFadeAnimation(data_.media_state);
1674 media_indicator_animation_->set_delegate(this);
1675 media_indicator_animation_->Start();
1678 void Tab::ScheduleIconPaint() {
1679 gfx::Rect bounds = GetIconBounds();
1680 if (bounds.IsEmpty())
1683 // Extends the area to the bottom when sad_favicon is
1685 if (IsPerformingCrashAnimation())
1686 bounds.set_height(height() - bounds.y());
1687 bounds.set_x(GetMirroredXForRect(bounds));
1688 SchedulePaintInRect(bounds);
1691 gfx::Rect Tab::GetImmersiveBarRect() const {
1692 // The main bar is as wide as the normal tab's horizontal top line.
1693 // This top line of the tab extends a few pixels left and right of the
1694 // center image due to pixels in the rounded corner images.
1695 const int kBarPadding = 1;
1696 int main_bar_left = tab_active_.l_width - kBarPadding;
1697 int main_bar_right = width() - tab_active_.r_width + kBarPadding;
1699 main_bar_left, 0, main_bar_right - main_bar_left, kImmersiveBarHeight);
1702 void Tab::GetTabIdAndFrameId(views::Widget* widget,
1704 int* frame_id) const {
1705 if (widget && widget->GetTopLevelWidget()->ShouldUseNativeFrame()) {
1706 *tab_id = IDR_THEME_TAB_BACKGROUND_V;
1708 } else if (data().incognito) {
1709 *tab_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO;
1710 *frame_id = IDR_THEME_FRAME_INCOGNITO;
1712 } else if (win8::IsSingleWindowMetroMode()) {
1713 *tab_id = IDR_THEME_TAB_BACKGROUND_V;
1717 *tab_id = IDR_THEME_TAB_BACKGROUND;
1718 *frame_id = IDR_THEME_FRAME;
1722 ////////////////////////////////////////////////////////////////////////////////
1723 // Tab, private static:
1726 void Tab::InitTabResources() {
1727 static bool initialized = false;
1733 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1734 font_ = new gfx::Font(rb.GetFont(ui::ResourceBundle::BaseFont));
1735 font_height_ = font_->GetHeight();
1737 image_cache_ = new ImageCache();
1739 // Load the tab images once now, and maybe again later if the theme changes.
1744 void Tab::LoadTabImages() {
1745 // We're not letting people override tab images just yet.
1746 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1748 tab_alpha_.image_l = rb.GetImageSkiaNamed(IDR_TAB_ALPHA_LEFT);
1749 tab_alpha_.image_r = rb.GetImageSkiaNamed(IDR_TAB_ALPHA_RIGHT);
1751 tab_active_.image_l = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_LEFT);
1752 tab_active_.image_c = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_CENTER);
1753 tab_active_.image_r = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_RIGHT);
1754 tab_active_.l_width = tab_active_.image_l->width();
1755 tab_active_.r_width = tab_active_.image_r->width();
1757 tab_inactive_.image_l = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_LEFT);
1758 tab_inactive_.image_c = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_CENTER);
1759 tab_inactive_.image_r = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_RIGHT);
1760 tab_inactive_.l_width = tab_inactive_.image_l->width();
1761 tab_inactive_.r_width = tab_inactive_.image_r->width();
1765 gfx::ImageSkia Tab::GetCachedImage(int resource_id,
1766 const gfx::Size& size,
1767 ui::ScaleFactor scale_factor) {
1768 for (ImageCache::const_iterator i = image_cache_->begin();
1769 i != image_cache_->end(); ++i) {
1770 if (i->resource_id == resource_id && i->scale_factor == scale_factor &&
1771 i->image.size() == size) {
1775 return gfx::ImageSkia();
1779 void Tab::SetCachedImage(int resource_id,
1780 ui::ScaleFactor scale_factor,
1781 const gfx::ImageSkia& image) {
1782 DCHECK_NE(scale_factor, ui::SCALE_FACTOR_NONE);
1783 ImageCacheEntry entry;
1784 entry.resource_id = resource_id;
1785 entry.scale_factor = scale_factor;
1786 entry.image = image;
1787 image_cache_->push_front(entry);
1788 if (image_cache_->size() > kMaxImageCacheSize)
1789 image_cache_->pop_back();