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