Index: chrome/browser/ui/views/tabs/tab.cc |
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc |
index ff64038d66a9a274103338c77e8c3abfd468707d..05c5fa3be7c8fb1c6f52d07b022c2ed2f9a01866 100644 |
--- a/chrome/browser/ui/views/tabs/tab.cc |
+++ b/chrome/browser/ui/views/tabs/tab.cc |
@@ -1,1656 +1,1634 @@ |
-// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-#include "chrome/browser/ui/views/tabs/tab.h" |
- |
-#include <limits> |
- |
-#include "base/command_line.h" |
-#include "base/debug/alias.h" |
-#include "base/strings/utf_string_conversions.h" |
-#include "chrome/browser/defaults.h" |
-#include "chrome/browser/themes/theme_properties.h" |
-#include "chrome/browser/ui/browser.h" |
-#include "chrome/browser/ui/tab_contents/core_tab_helper.h" |
-#include "chrome/browser/ui/tabs/tab_resources.h" |
-#include "chrome/browser/ui/tabs/tab_utils.h" |
-#include "chrome/browser/ui/view_ids.h" |
-#include "chrome/browser/ui/views/tabs/tab_controller.h" |
-#include "chrome/browser/ui/views/theme_image_mapper.h" |
-#include "chrome/browser/ui/views/touch_uma/touch_uma.h" |
-#include "chrome/common/chrome_switches.h" |
-#include "grit/generated_resources.h" |
-#include "grit/theme_resources.h" |
-#include "grit/ui_resources.h" |
-#include "third_party/skia/include/effects/SkGradientShader.h" |
-#include "ui/accessibility/ax_view_state.h" |
-#include "ui/base/l10n/l10n_util.h" |
-#include "ui/base/models/list_selection_model.h" |
-#include "ui/base/resource/resource_bundle.h" |
-#include "ui/base/theme_provider.h" |
-#include "ui/gfx/animation/animation_container.h" |
-#include "ui/gfx/animation/multi_animation.h" |
-#include "ui/gfx/animation/throb_animation.h" |
-#include "ui/gfx/canvas.h" |
-#include "ui/gfx/color_analysis.h" |
-#include "ui/gfx/favicon_size.h" |
-#include "ui/gfx/font.h" |
-#include "ui/gfx/image/image_skia_operations.h" |
-#include "ui/gfx/path.h" |
-#include "ui/gfx/rect_conversions.h" |
-#include "ui/gfx/skia_util.h" |
-#include "ui/gfx/text_elider.h" |
-#include "ui/views/border.h" |
-#include "ui/views/controls/button/image_button.h" |
-#include "ui/views/rect_based_targeting_utils.h" |
-#include "ui/views/widget/tooltip_manager.h" |
-#include "ui/views/widget/widget.h" |
-#include "ui/views/window/non_client_view.h" |
- |
-#if defined(USE_ASH) |
-#include "ui/aura/env.h" |
-#endif |
- |
-namespace { |
- |
-// Padding around the "content" of a tab, occupied by the tab border graphics. |
-const int kLeftPadding = 22; |
-const int kTopPadding = 7; |
-const int kRightPadding = 17; |
-const int kBottomPadding = 5; |
- |
-// Height of the shadow at the top of the tab image assets. |
-const int kDropShadowHeight = 4; |
- |
-// How long the pulse throb takes. |
-const int kPulseDurationMs = 200; |
- |
-// Width of touch tabs. |
-static const int kTouchWidth = 120; |
- |
-static const int kToolbarOverlap = 1; |
-static const int kFaviconTitleSpacing = 4; |
-// Additional vertical offset for title text relative to top of tab. |
-// Ash text rendering may be different than Windows. |
-static const int kTitleTextOffsetYAsh = 1; |
-static const int kTitleTextOffsetY = 0; |
-static const int kTitleCloseButtonSpacing = 3; |
-static const int kStandardTitleWidth = 175; |
-// Additional vertical offset for close button relative to top of tab. |
-// Ash needs this to match the text vertical position. |
-static const int kCloseButtonVertFuzzAsh = 1; |
-static const int kCloseButtonVertFuzz = 0; |
-// Additional horizontal offset for close button relative to title text. |
-static const int kCloseButtonHorzFuzz = 3; |
- |
-// When a non-mini-tab becomes a mini-tab the width of the tab animates. If |
-// the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab |
-// is rendered as a normal tab. This is done to avoid having the title |
-// immediately disappear when transitioning a tab from normal to mini-tab. |
-static const int kMiniTabRendererAsNormalTabWidth = |
- browser_defaults::kMiniTabWidth + 30; |
- |
-// How opaque to make the hover state (out of 1). |
-static const double kHoverOpacity = 0.33; |
- |
-// Opacity for non-active selected tabs. |
-static const double kSelectedTabOpacity = .45; |
- |
-// Selected (but not active) tabs have their throb value scaled down by this. |
-static const double kSelectedTabThrobScale = .5; |
- |
-// Durations for the various parts of the mini tab title animation. |
-static const int kMiniTitleChangeAnimationDuration1MS = 1600; |
-static const int kMiniTitleChangeAnimationStart1MS = 0; |
-static const int kMiniTitleChangeAnimationEnd1MS = 1900; |
-static const int kMiniTitleChangeAnimationDuration2MS = 0; |
-static const int kMiniTitleChangeAnimationDuration3MS = 550; |
-static const int kMiniTitleChangeAnimationStart3MS = 150; |
-static const int kMiniTitleChangeAnimationEnd3MS = 800; |
-static const int kMiniTitleChangeAnimationIntervalMS = 40; |
- |
-// Offset from the right edge for the start of the mini title change animation. |
-static const int kMiniTitleChangeInitialXOffset = 6; |
- |
-// Radius of the radial gradient used for mini title change animation. |
-static const int kMiniTitleChangeGradientRadius = 20; |
- |
-// Colors of the gradient used during the mini title change animation. |
-static const SkColor kMiniTitleChangeGradientColor1 = SK_ColorWHITE; |
-static const SkColor kMiniTitleChangeGradientColor2 = |
- SkColorSetARGB(0, 255, 255, 255); |
- |
-// Max number of images to cache. This has to be at least two since rounding |
-// errors may lead to tabs in the same tabstrip having different sizes. |
-const size_t kMaxImageCacheSize = 4; |
- |
-// Height of the miniature tab strip in immersive mode. |
-const int kImmersiveTabHeight = 3; |
- |
-// Height of the small tab indicator rectangles in immersive mode. |
-const int kImmersiveBarHeight = 2; |
- |
-// Color for active and inactive tabs in the immersive mode light strip. These |
-// should be a little brighter than the color of the normal art assets for tabs, |
-// which for active tabs is 230, 230, 230 and for inactive is 184, 184, 184. |
-const SkColor kImmersiveActiveTabColor = SkColorSetRGB(235, 235, 235); |
-const SkColor kImmersiveInactiveTabColor = SkColorSetRGB(190, 190, 190); |
- |
-// The minimum opacity (out of 1) when a tab (either active or inactive) is |
-// throbbing in the immersive mode light strip. |
-const double kImmersiveTabMinThrobOpacity = 0.66; |
- |
-// Number of steps in the immersive mode loading animation. |
-const int kImmersiveLoadingStepCount = 32; |
- |
-const char kTabCloseButtonName[] = "TabCloseButton"; |
- |
-void DrawIconAtLocation(gfx::Canvas* canvas, |
- const gfx::ImageSkia& image, |
- int image_offset, |
- int dst_x, |
- int dst_y, |
- int icon_width, |
- int icon_height, |
- bool filter, |
- const SkPaint& paint) { |
- // NOTE: the clipping is a work around for 69528, it shouldn't be necessary. |
- canvas->Save(); |
- canvas->ClipRect(gfx::Rect(dst_x, dst_y, icon_width, icon_height)); |
- canvas->DrawImageInt(image, |
- image_offset, 0, icon_width, icon_height, |
- dst_x, dst_y, icon_width, icon_height, |
- filter, paint); |
- canvas->Restore(); |
-} |
- |
-// Draws the icon image at the center of |bounds|. |
-void DrawIconCenter(gfx::Canvas* canvas, |
- const gfx::ImageSkia& image, |
- int image_offset, |
- int icon_width, |
- int icon_height, |
- const gfx::Rect& bounds, |
- bool filter, |
- const SkPaint& paint) { |
- // Center the image within bounds. |
- int dst_x = bounds.x() - (icon_width - bounds.width()) / 2; |
- int dst_y = bounds.y() - (icon_height - bounds.height()) / 2; |
- DrawIconAtLocation(canvas, image, image_offset, dst_x, dst_y, icon_width, |
- icon_height, filter, paint); |
-} |
- |
-chrome::HostDesktopType GetHostDesktopType(views::View* view) { |
- // Widget is NULL when tabs are detached. |
- views::Widget* widget = view->GetWidget(); |
- return chrome::GetHostDesktopTypeForNativeView( |
- widget ? widget->GetNativeView() : NULL); |
-} |
- |
-} // namespace |
- |
-//////////////////////////////////////////////////////////////////////////////// |
-// FaviconCrashAnimation |
-// |
-// A custom animation subclass to manage the favicon crash animation. |
-class Tab::FaviconCrashAnimation : public gfx::LinearAnimation, |
- public gfx::AnimationDelegate { |
- public: |
- explicit FaviconCrashAnimation(Tab* target) |
- : gfx::LinearAnimation(1000, 25, this), |
- target_(target) { |
- } |
- virtual ~FaviconCrashAnimation() {} |
- |
- // gfx::Animation overrides: |
- virtual void AnimateToState(double state) OVERRIDE { |
- const double kHidingOffset = 27; |
- |
- if (state < .5) { |
- target_->SetFaviconHidingOffset( |
- static_cast<int>(floor(kHidingOffset * 2.0 * state))); |
- } else { |
- target_->DisplayCrashedFavicon(); |
- target_->SetFaviconHidingOffset( |
- static_cast<int>( |
- floor(kHidingOffset - ((state - .5) * 2.0 * kHidingOffset)))); |
- } |
- } |
- |
- // gfx::AnimationDelegate overrides: |
- virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE { |
- target_->SetFaviconHidingOffset(0); |
- } |
- |
- private: |
- Tab* target_; |
- |
- DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation); |
-}; |
- |
-//////////////////////////////////////////////////////////////////////////////// |
-// TabCloseButton |
-// |
-// This is a Button subclass that causes middle clicks to be forwarded to the |
-// parent View by explicitly not handling them in OnMousePressed. |
-class Tab::TabCloseButton : public views::ImageButton { |
- public: |
- explicit TabCloseButton(Tab* tab) : views::ImageButton(tab), tab_(tab) {} |
- virtual ~TabCloseButton() {} |
- |
- // Overridden from views::View. |
- virtual View* GetEventHandlerForRect(const gfx::Rect& rect) OVERRIDE { |
- if (!views::UsePointBasedTargeting(rect)) |
- return View::GetEventHandlerForRect(rect); |
- |
- // Ignore the padding set on the button. |
- gfx::Rect contents_bounds = GetContentsBounds(); |
- contents_bounds.set_x(GetMirroredXForRect(contents_bounds)); |
- |
- // TODO(tdanderson): Remove this ifdef if rect-based targeting |
- // is turned on by default. |
-#if defined(USE_ASH) |
- // Include the padding in hit-test for touch events. |
- if (aura::Env::GetInstance()->is_touch_down()) |
- contents_bounds = GetLocalBounds(); |
-#elif defined(OS_WIN) |
- // TODO(sky): Use local-bounds if a touch-point is active. |
- // http://crbug.com/145258 |
-#endif |
- |
- return contents_bounds.Intersects(rect) ? this : parent(); |
- } |
- |
- // Overridden from views::View. |
- virtual View* GetTooltipHandlerForPoint(const gfx::Point& point) OVERRIDE { |
- // Tab close button has no children, so tooltip handler should be the same |
- // as the event handler. |
- // In addition, a hit test has to be performed for the point (as |
- // GetTooltipHandlerForPoint() is responsible for it). |
- if (!HitTestPoint(point)) |
- return NULL; |
- return GetEventHandlerForPoint(point); |
- } |
- |
- virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE { |
- tab_->controller_->OnMouseEventInTab(this, event); |
- |
- bool handled = ImageButton::OnMousePressed(event); |
- // Explicitly mark midle-mouse clicks as non-handled to ensure the tab |
- // sees them. |
- return event.IsOnlyMiddleMouseButton() ? false : handled; |
- } |
- |
- virtual void OnMouseMoved(const ui::MouseEvent& event) OVERRIDE { |
- tab_->controller_->OnMouseEventInTab(this, event); |
- CustomButton::OnMouseMoved(event); |
- } |
- |
- virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE { |
- tab_->controller_->OnMouseEventInTab(this, event); |
- CustomButton::OnMouseReleased(event); |
- } |
- |
- virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { |
- // Consume all gesture events here so that the parent (Tab) does not |
- // start consuming gestures. |
- ImageButton::OnGestureEvent(event); |
- event->SetHandled(); |
- } |
- |
- virtual bool HasHitTestMask() const OVERRIDE { |
- return true; |
- } |
- |
- virtual void GetHitTestMask(HitTestSource source, |
- gfx::Path* path) const OVERRIDE { |
- // Use the button's contents bounds (which does not include padding) |
- // and the hit test mask of our parent |tab_| to determine if the |
- // button is hidden behind another tab. |
- gfx::Path tab_mask; |
- tab_->GetHitTestMask(source, &tab_mask); |
- |
- gfx::Rect button_bounds(GetContentsBounds()); |
- button_bounds.set_x(GetMirroredXForRect(button_bounds)); |
- gfx::RectF tab_bounds_f(gfx::SkRectToRectF(tab_mask.getBounds())); |
- views::View::ConvertRectToTarget(tab_, this, &tab_bounds_f); |
- gfx::Rect tab_bounds = gfx::ToEnclosingRect(tab_bounds_f); |
- |
- // If either the top or bottom of the tab close button is clipped, |
- // do not consider these regions to be part of the button's bounds. |
- int top_overflow = tab_bounds.y() - button_bounds.y(); |
- int bottom_overflow = button_bounds.bottom() - tab_bounds.bottom(); |
- if (top_overflow > 0) |
- button_bounds.set_y(tab_bounds.y()); |
- else if (bottom_overflow > 0) |
- button_bounds.set_height(button_bounds.height() - bottom_overflow); |
- |
- // If the hit test request is in response to a gesture, |path| should be |
- // empty unless the entire tab close button is visible to the user. Hit |
- // test requests in response to a mouse event should always set |path| |
- // to be the visible portion of the tab close button, even if it is |
- // partially hidden behind another tab. |
- path->reset(); |
- gfx::Rect intersection(gfx::IntersectRects(tab_bounds, button_bounds)); |
- if (!intersection.IsEmpty()) { |
- // TODO(tdanderson): Consider always returning the intersection if |
- // the non-rectangular shape of the tabs can be accounted for. |
- if (source == HIT_TEST_SOURCE_TOUCH && |
- !tab_bounds.Contains(button_bounds)) |
- return; |
- |
- path->addRect(RectToSkRect(intersection)); |
- } |
- } |
- |
- virtual const char* GetClassName() const OVERRIDE { |
- return kTabCloseButtonName; |
- } |
- |
- private: |
- Tab* tab_; |
- |
- DISALLOW_COPY_AND_ASSIGN(TabCloseButton); |
-}; |
- |
-//////////////////////////////////////////////////////////////////////////////// |
-// ImageCacheEntry |
- |
-Tab::ImageCacheEntry::ImageCacheEntry() |
- : resource_id(-1), |
- scale_factor(ui::SCALE_FACTOR_NONE) { |
-} |
- |
-Tab::ImageCacheEntry::~ImageCacheEntry() {} |
- |
-//////////////////////////////////////////////////////////////////////////////// |
-// Tab, statics: |
- |
-// static |
-const char Tab::kViewClassName[] = "Tab"; |
- |
-// static |
-Tab::TabImage Tab::tab_alpha_ = {0}; |
-Tab::TabImage Tab::tab_active_ = {0}; |
-Tab::TabImage Tab::tab_inactive_ = {0}; |
-// static |
-gfx::Font* Tab::font_ = NULL; |
-// static |
-int Tab::font_height_ = 0; |
-// static |
-Tab::ImageCache* Tab::image_cache_ = NULL; |
- |
-//////////////////////////////////////////////////////////////////////////////// |
-// Tab, public: |
- |
-Tab::Tab(TabController* controller) |
- : controller_(controller), |
- closing_(false), |
- dragging_(false), |
- favicon_hiding_offset_(0), |
- loading_animation_frame_(0), |
- immersive_loading_step_(0), |
- should_display_crashed_favicon_(false), |
- animating_media_state_(TAB_MEDIA_STATE_NONE), |
- tab_activated_with_last_gesture_begin_(false), |
- hover_controller_(this), |
- showing_icon_(false), |
- showing_media_indicator_(false), |
- showing_close_button_(false), |
- close_button_color_(0) { |
- DCHECK(controller); |
- InitTabResources(); |
- |
- // So we get don't get enter/exit on children and don't prematurely stop the |
- // hover. |
- set_notify_enter_exit_on_child(true); |
- |
- set_id(VIEW_ID_TAB); |
- |
- // Add the Close Button. |
- close_button_ = new TabCloseButton(this); |
- ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
- close_button_->SetImage(views::CustomButton::STATE_NORMAL, |
- rb.GetImageSkiaNamed(IDR_CLOSE_1)); |
- close_button_->SetImage(views::CustomButton::STATE_HOVERED, |
- rb.GetImageSkiaNamed(IDR_CLOSE_1_H)); |
- close_button_->SetImage(views::CustomButton::STATE_PRESSED, |
- rb.GetImageSkiaNamed(IDR_CLOSE_1_P)); |
- close_button_->SetAccessibleName( |
- l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE)); |
- // Disable animation so that the red danger sign shows up immediately |
- // to help avoid mis-clicks. |
- close_button_->SetAnimationDuration(0); |
- AddChildView(close_button_); |
- |
- set_context_menu_controller(this); |
-} |
- |
-Tab::~Tab() { |
-} |
- |
-void Tab::set_animation_container(gfx::AnimationContainer* container) { |
- animation_container_ = container; |
- hover_controller_.SetAnimationContainer(container); |
-} |
- |
-bool Tab::IsActive() const { |
- return controller_->IsActiveTab(this); |
-} |
- |
-bool Tab::IsSelected() const { |
- return controller_->IsTabSelected(this); |
-} |
- |
-void Tab::SetData(const TabRendererData& data) { |
- if (data_.Equals(data)) |
- return; |
- |
- TabRendererData old(data_); |
- data_ = data; |
- |
- if (data_.IsCrashed()) { |
- if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation()) { |
- data_.media_state = TAB_MEDIA_STATE_NONE; |
-#if defined(OS_CHROMEOS) |
- // On Chrome OS, we reload killed tabs automatically when the user |
- // switches to them. Don't display animations for these unless they're |
- // selected (i.e. in the foreground) -- we won't reload these |
- // automatically since we don't want to get into a crash loop. |
- if (IsSelected() || |
- data_.crashed_status != base::TERMINATION_STATUS_PROCESS_WAS_KILLED) |
- StartCrashAnimation(); |
-#else |
- StartCrashAnimation(); |
-#endif |
- } |
- } else { |
- if (IsPerformingCrashAnimation()) |
- StopCrashAnimation(); |
- ResetCrashedFavicon(); |
- } |
- |
- if (data_.media_state != old.media_state) { |
- if (data_.media_state != TAB_MEDIA_STATE_NONE) |
- animating_media_state_ = data_.media_state; |
- StartMediaIndicatorAnimation(); |
- } |
- |
- if (old.mini != data_.mini) { |
- if (tab_animation_.get() && tab_animation_->is_animating()) { |
- tab_animation_->Stop(); |
- tab_animation_.reset(NULL); |
- } |
- } |
- |
- DataChanged(old); |
- |
- Layout(); |
- SchedulePaint(); |
-} |
- |
-void Tab::UpdateLoadingAnimation(TabRendererData::NetworkState state) { |
- if (state == data_.network_state && |
- state == TabRendererData::NETWORK_STATE_NONE) { |
- // If the network state is none and hasn't changed, do nothing. Otherwise we |
- // need to advance the animation frame. |
- return; |
- } |
- |
- TabRendererData::NetworkState old_state = data_.network_state; |
- data_.network_state = state; |
- AdvanceLoadingAnimation(old_state, state); |
-} |
- |
-void Tab::StartPulse() { |
- gfx::ThrobAnimation* animation = new gfx::ThrobAnimation(this); |
- animation->SetSlideDuration(kPulseDurationMs); |
- if (animation_container_.get()) |
- animation->SetContainer(animation_container_.get()); |
- animation->StartThrobbing(std::numeric_limits<int>::max()); |
- tab_animation_.reset(animation); |
-} |
- |
-void Tab::StopPulse() { |
- if (!tab_animation_.get()) |
- return; |
- tab_animation_->Stop(); |
- tab_animation_.reset(NULL); |
-} |
- |
-void Tab::StartMiniTabTitleAnimation() { |
- // We can only do this animation if the tab is mini because we will |
- // upcast tab_animation back to MultiAnimation when we draw. |
- if (!data().mini) |
- return; |
- if (!tab_animation_.get()) { |
- gfx::MultiAnimation::Parts parts; |
- parts.push_back( |
- gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration1MS, |
- gfx::Tween::EASE_OUT)); |
- parts.push_back( |
- gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration2MS, |
- gfx::Tween::ZERO)); |
- parts.push_back( |
- gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration3MS, |
- gfx::Tween::EASE_IN)); |
- parts[0].start_time_ms = kMiniTitleChangeAnimationStart1MS; |
- parts[0].end_time_ms = kMiniTitleChangeAnimationEnd1MS; |
- parts[2].start_time_ms = kMiniTitleChangeAnimationStart3MS; |
- parts[2].end_time_ms = kMiniTitleChangeAnimationEnd3MS; |
- base::TimeDelta timeout = |
- base::TimeDelta::FromMilliseconds(kMiniTitleChangeAnimationIntervalMS); |
- gfx::MultiAnimation* animation = new gfx::MultiAnimation(parts, timeout); |
- if (animation_container_.get()) |
- animation->SetContainer(animation_container_.get()); |
- animation->set_delegate(this); |
- tab_animation_.reset(animation); |
- } |
- tab_animation_->Start(); |
-} |
- |
-void Tab::StopMiniTabTitleAnimation() { |
- if (!tab_animation_.get()) |
- return; |
- tab_animation_->Stop(); |
- tab_animation_.reset(NULL); |
-} |
- |
-// static |
-gfx::Size Tab::GetBasicMinimumUnselectedSize() { |
- InitTabResources(); |
- |
- gfx::Size minimum_size; |
- minimum_size.set_width(kLeftPadding + kRightPadding); |
- // Since we use image images, the real minimum height of the image is |
- // defined most accurately by the height of the end cap images. |
- minimum_size.set_height(tab_active_.image_l->height()); |
- return minimum_size; |
-} |
- |
-gfx::Size Tab::GetMinimumUnselectedSize() { |
- return GetBasicMinimumUnselectedSize(); |
-} |
- |
-// static |
-gfx::Size Tab::GetMinimumSelectedSize() { |
- gfx::Size minimum_size = GetBasicMinimumUnselectedSize(); |
- minimum_size.set_width( |
- kLeftPadding + gfx::kFaviconSize + kRightPadding); |
- return minimum_size; |
-} |
- |
-// static |
-gfx::Size Tab::GetStandardSize() { |
- gfx::Size standard_size = GetBasicMinimumUnselectedSize(); |
- standard_size.set_width( |
- standard_size.width() + kFaviconTitleSpacing + kStandardTitleWidth); |
- return standard_size; |
-} |
- |
-// static |
-int Tab::GetTouchWidth() { |
- return kTouchWidth; |
-} |
- |
-// static |
-int Tab::GetMiniWidth() { |
- return browser_defaults::kMiniTabWidth; |
-} |
- |
-// static |
-int Tab::GetImmersiveHeight() { |
- return kImmersiveTabHeight; |
-} |
- |
-//////////////////////////////////////////////////////////////////////////////// |
-// Tab, AnimationDelegate overrides: |
- |
-void Tab::AnimationProgressed(const gfx::Animation* animation) { |
- // Ignore if the pulse animation is being performed on active tab because |
- // it repaints the same image. See |Tab::PaintTabBackground()|. |
- if (animation == tab_animation_.get() && IsActive()) |
- return; |
- SchedulePaint(); |
-} |
- |
-void Tab::AnimationCanceled(const gfx::Animation* animation) { |
- if (media_indicator_animation_ == animation) |
- animating_media_state_ = data_.media_state; |
- SchedulePaint(); |
-} |
- |
-void Tab::AnimationEnded(const gfx::Animation* animation) { |
- if (media_indicator_animation_ == animation) |
- animating_media_state_ = data_.media_state; |
- SchedulePaint(); |
-} |
- |
-//////////////////////////////////////////////////////////////////////////////// |
-// Tab, views::ButtonListener overrides: |
- |
-void Tab::ButtonPressed(views::Button* sender, const ui::Event& event) { |
- const CloseTabSource source = |
- (event.type() == ui::ET_MOUSE_RELEASED && |
- (event.flags() & ui::EF_FROM_TOUCH) == 0) ? CLOSE_TAB_FROM_MOUSE : |
- CLOSE_TAB_FROM_TOUCH; |
- DCHECK_EQ(close_button_, sender); |
- controller_->CloseTab(this, source); |
- if (event.type() == ui::ET_GESTURE_TAP) |
- TouchUMA::RecordGestureAction(TouchUMA::GESTURE_TABCLOSE_TAP); |
-} |
- |
-//////////////////////////////////////////////////////////////////////////////// |
-// Tab, views::ContextMenuController overrides: |
- |
-void Tab::ShowContextMenuForView(views::View* source, |
- const gfx::Point& point, |
- ui::MenuSourceType source_type) { |
- if (!closing()) |
- controller_->ShowContextMenuForTab(this, point, source_type); |
-} |
- |
-//////////////////////////////////////////////////////////////////////////////// |
-// Tab, views::View overrides: |
- |
-void Tab::OnPaint(gfx::Canvas* canvas) { |
- // Don't paint if we're narrower than we can render correctly. (This should |
- // only happen during animations). |
- if (width() < GetMinimumUnselectedSize().width() && !data().mini) |
- return; |
- |
- gfx::Rect clip; |
- if (!controller_->ShouldPaintTab(this, &clip)) |
- return; |
- if (!clip.IsEmpty()) { |
- canvas->Save(); |
- canvas->ClipRect(clip); |
- } |
- |
- if (controller_->IsImmersiveStyle()) |
- PaintImmersiveTab(canvas); |
- else |
- PaintTab(canvas); |
- |
- if (!clip.IsEmpty()) |
- canvas->Restore(); |
-} |
- |
-void Tab::Layout() { |
- gfx::Rect lb = GetContentsBounds(); |
- if (lb.IsEmpty()) |
- return; |
- lb.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding); |
- |
- // The height of the content of the Tab is the largest of the favicon, |
- // the title text and the close button graphic. |
- const int kTabIconSize = gfx::kFaviconSize; |
- int content_height = std::max(kTabIconSize, font_height_); |
- close_button_->SetBorder(views::Border::NullBorder()); |
- gfx::Size close_button_size(close_button_->GetPreferredSize()); |
- content_height = std::max(content_height, close_button_size.height()); |
- |
- // Size the Favicon. |
- showing_icon_ = ShouldShowIcon(); |
- if (showing_icon_) { |
- // Use the size of the favicon as apps use a bigger favicon size. |
- int favicon_top = kTopPadding + content_height / 2 - kTabIconSize / 2; |
- int favicon_left = lb.x(); |
- favicon_bounds_.SetRect(favicon_left, favicon_top, |
- kTabIconSize, kTabIconSize); |
- MaybeAdjustLeftForMiniTab(&favicon_bounds_); |
- } else { |
- favicon_bounds_.SetRect(lb.x(), lb.y(), 0, 0); |
- } |
- |
- // Size the Close button. |
- showing_close_button_ = ShouldShowCloseBox(); |
- const bool is_host_desktop_type_ash = |
- GetHostDesktopType(this) == chrome::HOST_DESKTOP_TYPE_ASH; |
- if (showing_close_button_) { |
- const int close_button_vert_fuzz = is_host_desktop_type_ash ? |
- kCloseButtonVertFuzzAsh : kCloseButtonVertFuzz; |
- int close_button_top = kTopPadding + close_button_vert_fuzz + |
- (content_height - close_button_size.height()) / 2; |
- // If the ratio of the close button size to tab width exceeds the maximum. |
- // The close button should be as large as possible so that there is a larger |
- // hit-target for touch events. So the close button bounds extends to the |
- // edges of the tab. However, the larger hit-target should be active only |
- // for mouse events, and the close-image should show up in the right place. |
- // So a border is added to the button with necessary padding. The close |
- // button (BaseTab::TabCloseButton) makes sure the padding is a hit-target |
- // only for touch events. |
- int top_border = close_button_top; |
- int bottom_border = height() - (close_button_size.height() + top_border); |
- int left_border = kCloseButtonHorzFuzz; |
- int right_border = width() - (lb.width() + close_button_size.width() + |
- left_border); |
- close_button_->SetBorder(views::Border::CreateEmptyBorder( |
- top_border, left_border, bottom_border, right_border)); |
- close_button_->SetPosition(gfx::Point(lb.width(), 0)); |
- close_button_->SizeToPreferredSize(); |
- close_button_->SetVisible(true); |
- } else { |
- close_button_->SetBounds(0, 0, 0, 0); |
- close_button_->SetVisible(false); |
- } |
- |
- showing_media_indicator_ = ShouldShowMediaIndicator(); |
- if (showing_media_indicator_) { |
- const gfx::Image& media_indicator_image = |
- chrome::GetTabMediaIndicatorImage(animating_media_state_); |
- media_indicator_bounds_.set_width(media_indicator_image.Width()); |
- media_indicator_bounds_.set_height(media_indicator_image.Height()); |
- media_indicator_bounds_.set_y( |
- kTopPadding + |
- (content_height - media_indicator_bounds_.height()) / 2); |
- const int right = showing_close_button_ ? |
- close_button_->x() + close_button_->GetInsets().left() : lb.right(); |
- media_indicator_bounds_.set_x( |
- std::max(lb.x(), right - media_indicator_bounds_.width())); |
- MaybeAdjustLeftForMiniTab(&media_indicator_bounds_); |
- } else { |
- media_indicator_bounds_.SetRect(lb.x(), lb.y(), 0, 0); |
- } |
- |
- const int title_text_offset = is_host_desktop_type_ash ? |
- kTitleTextOffsetYAsh : kTitleTextOffsetY; |
- int title_left = favicon_bounds_.right() + kFaviconTitleSpacing; |
- int title_top = kTopPadding + title_text_offset + |
- (content_height - font_height_) / 2; |
- // Size the Title text to fill the remaining space. |
- if (!data().mini || width() >= kMiniTabRendererAsNormalTabWidth) { |
- // If the user has big fonts, the title will appear rendered too far down |
- // on the y-axis if we use the regular top padding, so we need to adjust it |
- // so that the text appears centered. |
- gfx::Size minimum_size = GetMinimumUnselectedSize(); |
- int text_height = title_top + font_height_ + kBottomPadding; |
- if (text_height > minimum_size.height()) |
- title_top -= (text_height - minimum_size.height()) / 2; |
- |
- int title_width; |
- if (showing_media_indicator_) { |
- title_width = media_indicator_bounds_.x() - kTitleCloseButtonSpacing - |
- title_left; |
- } else if (close_button_->visible()) { |
- // The close button has an empty border with some padding (see details |
- // above where the close-button's bounds is set). Allow the title to |
- // overlap the empty padding. |
- title_width = close_button_->x() + close_button_->GetInsets().left() - |
- kTitleCloseButtonSpacing - title_left; |
- } else { |
- title_width = lb.width() - title_left; |
- } |
- title_width = std::max(title_width, 0); |
- title_bounds_.SetRect(title_left, title_top, title_width, font_height_); |
- } else { |
- title_bounds_.SetRect(title_left, title_top, 0, 0); |
- } |
- |
- // Certain UI elements within the Tab (the favicon, etc.) are not represented |
- // as child Views (which is the preferred method). Instead, these UI elements |
- // are drawn directly on the canvas from within Tab::OnPaint(). The Tab's |
- // child Views (for example, the Tab's close button which is a views::Button |
- // instance) are automatically mirrored by the mirroring infrastructure in |
- // views. The elements Tab draws directly on the canvas need to be manually |
- // mirrored if the View's layout is right-to-left. |
- title_bounds_.set_x(GetMirroredXForRect(title_bounds_)); |
-} |
- |
-void Tab::OnThemeChanged() { |
- LoadTabImages(); |
-} |
- |
-const char* Tab::GetClassName() const { |
- return kViewClassName; |
-} |
- |
-bool Tab::HasHitTestMask() const { |
- return true; |
-} |
- |
-void Tab::GetHitTestMask(HitTestSource source, gfx::Path* path) const { |
- // When the window is maximized we don't want to shave off the edges or top |
- // shadow of the tab, such that the user can click anywhere along the top |
- // edge of the screen to select a tab. Ditto for immersive fullscreen. |
- const views::Widget* widget = GetWidget(); |
- bool include_top_shadow = |
- widget && (widget->IsMaximized() || widget->IsFullscreen()); |
- TabResources::GetHitTestMask(width(), height(), include_top_shadow, path); |
- |
- // It is possible for a portion of the tab to be occluded if tabs are |
- // stacked, so modify the hit test mask to only include the visible |
- // region of the tab. |
- gfx::Rect clip; |
- controller_->ShouldPaintTab(this, &clip); |
- if (clip.size().GetArea()) { |
- SkRect intersection(path->getBounds()); |
- intersection.intersect(RectToSkRect(clip)); |
- path->reset(); |
- path->addRect(intersection); |
- } |
-} |
- |
-bool Tab::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const { |
- // Note: Anything that affects the tooltip text should be accounted for when |
- // calling TooltipTextChanged() from Tab::DataChanged(). |
- *tooltip = chrome::AssembleTabTooltipText(data_.title, data_.media_state); |
- return !tooltip->empty(); |
-} |
- |
-bool Tab::GetTooltipTextOrigin(const gfx::Point& p, gfx::Point* origin) const { |
- origin->set_x(title_bounds_.x() + 10); |
- origin->set_y(-views::TooltipManager::GetTooltipHeight() - 4); |
- return true; |
-} |
- |
-bool Tab::OnMousePressed(const ui::MouseEvent& event) { |
- controller_->OnMouseEventInTab(this, event); |
- |
- // Allow a right click from touch to drag, which corresponds to a long click. |
- if (event.IsOnlyLeftMouseButton() || |
- (event.IsOnlyRightMouseButton() && event.flags() & ui::EF_FROM_TOUCH)) { |
- ui::ListSelectionModel original_selection; |
- original_selection.Copy(controller_->GetSelectionModel()); |
- // Changing the selection may cause our bounds to change. If that happens |
- // the location of the event may no longer be valid. Create a copy of the |
- // event in the parents coordinate, which won't change, and recreate an |
- // event after changing so the coordinates are correct. |
- ui::MouseEvent event_in_parent(event, static_cast<View*>(this), parent()); |
- if (controller_->SupportsMultipleSelection()) { |
- if (event.IsShiftDown() && event.IsControlDown()) { |
- controller_->AddSelectionFromAnchorTo(this); |
- } else if (event.IsShiftDown()) { |
- controller_->ExtendSelectionTo(this); |
- } else if (event.IsControlDown()) { |
- controller_->ToggleSelected(this); |
- if (!IsSelected()) { |
- // Don't allow dragging non-selected tabs. |
- return false; |
- } |
- } else if (!IsSelected()) { |
- controller_->SelectTab(this); |
- } |
- } else if (!IsSelected()) { |
- controller_->SelectTab(this); |
- } |
- ui::MouseEvent cloned_event(event_in_parent, parent(), |
- static_cast<View*>(this)); |
- controller_->MaybeStartDrag(this, cloned_event, original_selection); |
- } |
- return true; |
-} |
- |
-bool Tab::OnMouseDragged(const ui::MouseEvent& event) { |
- controller_->ContinueDrag(this, event); |
- return true; |
-} |
- |
-void Tab::OnMouseReleased(const ui::MouseEvent& event) { |
- controller_->OnMouseEventInTab(this, event); |
- |
- // Notify the drag helper that we're done with any potential drag operations. |
- // Clean up the drag helper, which is re-created on the next mouse press. |
- // In some cases, ending the drag will schedule the tab for destruction; if |
- // so, bail immediately, since our members are already dead and we shouldn't |
- // do anything else except drop the tab where it is. |
- if (controller_->EndDrag(END_DRAG_COMPLETE)) |
- return; |
- |
- // Close tab on middle click, but only if the button is released over the tab |
- // (normal windows behavior is to discard presses of a UI element where the |
- // releases happen off the element). |
- if (event.IsMiddleMouseButton()) { |
- if (HitTestPoint(event.location())) { |
- controller_->CloseTab(this, CLOSE_TAB_FROM_MOUSE); |
- } else if (closing_) { |
- // We're animating closed and a middle mouse button was pushed on us but |
- // we don't contain the mouse anymore. We assume the user is clicking |
- // quicker than the animation and we should close the tab that falls under |
- // the mouse. |
- Tab* closest_tab = controller_->GetTabAt(this, event.location()); |
- if (closest_tab) |
- controller_->CloseTab(closest_tab, CLOSE_TAB_FROM_MOUSE); |
- } |
- } else if (event.IsOnlyLeftMouseButton() && !event.IsShiftDown() && |
- !event.IsControlDown()) { |
- // If the tab was already selected mouse pressed doesn't change the |
- // selection. Reset it now to handle the case where multiple tabs were |
- // selected. |
- controller_->SelectTab(this); |
- } |
-} |
- |
-void Tab::OnMouseCaptureLost() { |
- controller_->EndDrag(END_DRAG_CAPTURE_LOST); |
-} |
- |
-void Tab::OnMouseEntered(const ui::MouseEvent& event) { |
- hover_controller_.Show(views::GlowHoverController::SUBTLE); |
-} |
- |
-void Tab::OnMouseMoved(const ui::MouseEvent& event) { |
- hover_controller_.SetLocation(event.location()); |
- controller_->OnMouseEventInTab(this, event); |
-} |
- |
-void Tab::OnMouseExited(const ui::MouseEvent& event) { |
- hover_controller_.Hide(); |
-} |
- |
-void Tab::OnGestureEvent(ui::GestureEvent* event) { |
- switch (event->type()) { |
- case ui::ET_GESTURE_BEGIN: { |
- if (event->details().touch_points() != 1) |
- return; |
- |
- // See comment in OnMousePressed() as to why we copy the event. |
- ui::GestureEvent event_in_parent(*event, static_cast<View*>(this), |
- parent()); |
- ui::ListSelectionModel original_selection; |
- original_selection.Copy(controller_->GetSelectionModel()); |
- tab_activated_with_last_gesture_begin_ = !IsActive(); |
- if (!IsSelected()) |
- controller_->SelectTab(this); |
- gfx::Point loc(event->location()); |
- views::View::ConvertPointToScreen(this, &loc); |
- ui::GestureEvent cloned_event(event_in_parent, parent(), |
- static_cast<View*>(this)); |
- controller_->MaybeStartDrag(this, cloned_event, original_selection); |
- break; |
- } |
- |
- case ui::ET_GESTURE_END: |
- controller_->EndDrag(END_DRAG_COMPLETE); |
- break; |
- |
- case ui::ET_GESTURE_SCROLL_UPDATE: |
- controller_->ContinueDrag(this, *event); |
- break; |
- |
- default: |
- break; |
- } |
- event->SetHandled(); |
-} |
- |
-void Tab::GetAccessibleState(ui::AXViewState* state) { |
- state->role = ui::AX_ROLE_TAB; |
- state->name = data_.title; |
-} |
- |
-//////////////////////////////////////////////////////////////////////////////// |
-// Tab, private |
- |
-const gfx::Rect& Tab::GetTitleBounds() const { |
- return title_bounds_; |
-} |
- |
-const gfx::Rect& Tab::GetIconBounds() const { |
- return favicon_bounds_; |
-} |
- |
-void Tab::MaybeAdjustLeftForMiniTab(gfx::Rect* bounds) const { |
- if (!data().mini || width() >= kMiniTabRendererAsNormalTabWidth) |
- return; |
- const int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth(); |
- const int ideal_delta = width() - GetMiniWidth(); |
- const int ideal_x = (GetMiniWidth() - bounds->width()) / 2; |
- bounds->set_x(bounds->x() + static_cast<int>( |
- (1 - static_cast<float>(ideal_delta) / static_cast<float>(mini_delta)) * |
- (ideal_x - bounds->x()))); |
-} |
- |
-void Tab::DataChanged(const TabRendererData& old) { |
- if (data().media_state != old.media_state || data().title != old.title) |
- TooltipTextChanged(); |
- |
- if (data().blocked == old.blocked) |
- return; |
- |
- if (data().blocked) |
- StartPulse(); |
- else |
- StopPulse(); |
-} |
- |
-void Tab::PaintTab(gfx::Canvas* canvas) { |
- // See if the model changes whether the icons should be painted. |
- const bool show_icon = ShouldShowIcon(); |
- const bool show_media_indicator = ShouldShowMediaIndicator(); |
- const bool show_close_button = ShouldShowCloseBox(); |
- if (show_icon != showing_icon_ || |
- show_media_indicator != showing_media_indicator_ || |
- show_close_button != showing_close_button_) { |
- Layout(); |
- } |
- |
- PaintTabBackground(canvas); |
- |
- SkColor title_color = GetThemeProvider()-> |
- GetColor(IsSelected() ? |
- ThemeProperties::COLOR_TAB_TEXT : |
- ThemeProperties::COLOR_BACKGROUND_TAB_TEXT); |
- |
- if (!data().mini || width() > kMiniTabRendererAsNormalTabWidth) |
- PaintTitle(canvas, title_color); |
- |
- if (show_icon) |
- PaintIcon(canvas); |
- |
- if (show_media_indicator) |
- PaintMediaIndicator(canvas); |
- |
- // If the close button color has changed, generate a new one. |
- if (!close_button_color_ || title_color != close_button_color_) { |
- close_button_color_ = title_color; |
- ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
- close_button_->SetBackground(close_button_color_, |
- rb.GetImageSkiaNamed(IDR_CLOSE_1), |
- rb.GetImageSkiaNamed(IDR_CLOSE_1_MASK)); |
- } |
-} |
- |
-void Tab::PaintImmersiveTab(gfx::Canvas* canvas) { |
- // Use transparency for the draw-attention animation. |
- int alpha = 255; |
- if (tab_animation_ && |
- tab_animation_->is_animating() && |
- !data().mini) { |
- alpha = tab_animation_->CurrentValueBetween( |
- 255, static_cast<int>(255 * kImmersiveTabMinThrobOpacity)); |
- } |
- |
- // Draw a gray rectangle to represent the tab. This works for mini-tabs as |
- // well as regular ones. The active tab has a brigher bar. |
- SkColor color = |
- IsActive() ? kImmersiveActiveTabColor : kImmersiveInactiveTabColor; |
- gfx::Rect bar_rect = GetImmersiveBarRect(); |
- canvas->FillRect(bar_rect, SkColorSetA(color, alpha)); |
- |
- // Paint network activity indicator. |
- // TODO(jamescook): Replace this placeholder animation with a real one. |
- // For now, let's go with a Cylon eye effect, but in blue. |
- if (data().network_state != TabRendererData::NETWORK_STATE_NONE) { |
- const SkColor kEyeColor = SkColorSetARGB(alpha, 71, 138, 217); |
- int eye_width = bar_rect.width() / 3; |
- int eye_offset = bar_rect.width() * immersive_loading_step_ / |
- kImmersiveLoadingStepCount; |
- if (eye_offset + eye_width < bar_rect.width()) { |
- // Draw a single indicator strip because it fits inside |bar_rect|. |
- gfx::Rect eye_rect( |
- bar_rect.x() + eye_offset, 0, eye_width, kImmersiveBarHeight); |
- canvas->FillRect(eye_rect, kEyeColor); |
- } else { |
- // Draw two indicators to simulate the eye "wrapping around" to the left |
- // side. The first part fills the remainder of the bar. |
- int right_eye_width = bar_rect.width() - eye_offset; |
- gfx::Rect right_eye_rect( |
- bar_rect.x() + eye_offset, 0, right_eye_width, kImmersiveBarHeight); |
- canvas->FillRect(right_eye_rect, kEyeColor); |
- // The second part parts the remaining |eye_width| on the left. |
- int left_eye_width = eye_offset + eye_width - bar_rect.width(); |
- gfx::Rect left_eye_rect( |
- bar_rect.x(), 0, left_eye_width, kImmersiveBarHeight); |
- canvas->FillRect(left_eye_rect, kEyeColor); |
- } |
- } |
-} |
- |
-void Tab::PaintTabBackground(gfx::Canvas* canvas) { |
- if (IsActive()) { |
- PaintActiveTabBackground(canvas); |
- } else { |
- if (tab_animation_.get() && |
- tab_animation_->is_animating() && |
- data().mini) { |
- gfx::MultiAnimation* animation = |
- static_cast<gfx::MultiAnimation*>(tab_animation_.get()); |
- PaintInactiveTabBackgroundWithTitleChange(canvas, animation); |
- } else { |
- PaintInactiveTabBackground(canvas); |
- } |
- |
- double throb_value = GetThrobValue(); |
- if (throb_value > 0) { |
- canvas->SaveLayerAlpha(static_cast<int>(throb_value * 0xff), |
- GetLocalBounds()); |
- PaintActiveTabBackground(canvas); |
- canvas->Restore(); |
- } |
- } |
-} |
- |
-void Tab::PaintInactiveTabBackgroundWithTitleChange( |
- gfx::Canvas* canvas, |
- gfx::MultiAnimation* animation) { |
- // Render the inactive tab background. We'll use this for clipping. |
- gfx::Canvas background_canvas(size(), canvas->image_scale(), false); |
- PaintInactiveTabBackground(&background_canvas); |
- |
- gfx::ImageSkia background_image(background_canvas.ExtractImageRep()); |
- |
- // Draw a radial gradient to hover_canvas. |
- gfx::Canvas hover_canvas(size(), canvas->image_scale(), false); |
- int radius = kMiniTitleChangeGradientRadius; |
- int x0 = width() + radius - kMiniTitleChangeInitialXOffset; |
- int x1 = radius; |
- int x2 = -radius; |
- int x; |
- if (animation->current_part_index() == 0) { |
- x = animation->CurrentValueBetween(x0, x1); |
- } else if (animation->current_part_index() == 1) { |
- x = x1; |
- } else { |
- x = animation->CurrentValueBetween(x1, x2); |
- } |
- SkPoint center_point; |
- center_point.iset(x, 0); |
- SkColor colors[2] = { kMiniTitleChangeGradientColor1, |
- kMiniTitleChangeGradientColor2 }; |
- skia::RefPtr<SkShader> shader = skia::AdoptRef( |
- SkGradientShader::CreateRadial( |
- center_point, SkIntToScalar(radius), colors, NULL, 2, |
- SkShader::kClamp_TileMode)); |
- SkPaint paint; |
- paint.setShader(shader.get()); |
- hover_canvas.DrawRect(gfx::Rect(x - radius, -radius, radius * 2, radius * 2), |
- paint); |
- |
- // Draw the radial gradient clipped to the background into hover_image. |
- gfx::ImageSkia hover_image = gfx::ImageSkiaOperations::CreateMaskedImage( |
- gfx::ImageSkia(hover_canvas.ExtractImageRep()), background_image); |
- |
- // Draw the tab background to the canvas. |
- canvas->DrawImageInt(background_image, 0, 0); |
- |
- // And then the gradient on top of that. |
- if (animation->current_part_index() == 2) { |
- uint8 alpha = animation->CurrentValueBetween(255, 0); |
- canvas->DrawImageInt(hover_image, 0, 0, alpha); |
- } else { |
- canvas->DrawImageInt(hover_image, 0, 0); |
- } |
-} |
- |
-void Tab::PaintInactiveTabBackground(gfx::Canvas* canvas) { |
- int tab_id; |
- int frame_id; |
- views::Widget* widget = GetWidget(); |
- GetTabIdAndFrameId(widget, &tab_id, &frame_id); |
- |
- // Explicitly map the id so we cache correctly. |
- const chrome::HostDesktopType host_desktop_type = GetHostDesktopType(this); |
- tab_id = chrome::MapThemeImage(host_desktop_type, tab_id); |
- |
- // HasCustomImage() is only true if the theme provides the image. However, |
- // even if the theme does not provide a tab background, the theme machinery |
- // will make one if given a frame image. |
- ui::ThemeProvider* theme_provider = GetThemeProvider(); |
- const bool theme_provided_image = theme_provider->HasCustomImage(tab_id) || |
- (frame_id != 0 && theme_provider->HasCustomImage(frame_id)); |
- |
- const bool can_cache = !theme_provided_image && |
- !hover_controller_.ShouldDraw(); |
- |
- if (can_cache) { |
- ui::ScaleFactor scale_factor = |
- ui::GetSupportedScaleFactor(canvas->image_scale()); |
- gfx::ImageSkia cached_image(GetCachedImage(tab_id, size(), scale_factor)); |
- if (cached_image.width() == 0) { |
- gfx::Canvas tmp_canvas(size(), canvas->image_scale(), false); |
- PaintInactiveTabBackgroundUsingResourceId(&tmp_canvas, tab_id); |
- cached_image = gfx::ImageSkia(tmp_canvas.ExtractImageRep()); |
- SetCachedImage(tab_id, scale_factor, cached_image); |
- } |
- canvas->DrawImageInt(cached_image, 0, 0); |
- } else { |
- PaintInactiveTabBackgroundUsingResourceId(canvas, tab_id); |
- } |
-} |
- |
-void Tab::PaintInactiveTabBackgroundUsingResourceId(gfx::Canvas* canvas, |
- int tab_id) { |
- // WARNING: the inactive tab background may be cached. If you change what it |
- // is drawn from you may need to update whether it can be cached. |
- |
- // The tab image needs to be lined up with the background image |
- // so that it feels partially transparent. These offsets represent the tab |
- // position within the frame background image. |
- int offset = GetMirroredX() + background_offset_.x(); |
- |
- gfx::ImageSkia* tab_bg = GetThemeProvider()->GetImageSkiaNamed(tab_id); |
- |
- TabImage* tab_image = &tab_active_; |
- TabImage* tab_inactive_image = &tab_inactive_; |
- TabImage* alpha = &tab_alpha_; |
- |
- // If the theme is providing a custom background image, then its top edge |
- // should be at the top of the tab. Otherwise, we assume that the background |
- // image is a composited foreground + frame image. |
- int bg_offset_y = GetThemeProvider()->HasCustomImage(tab_id) ? |
- 0 : background_offset_.y(); |
- |
- // We need a gfx::Canvas object to be able to extract the image from. |
- // We draw everything to this canvas and then output it to the canvas |
- // parameter in addition to using it to mask the hover glow if needed. |
- gfx::Canvas background_canvas(size(), canvas->image_scale(), false); |
- |
- // Draw left edge. Don't draw over the toolbar, as we're not the foreground |
- // tab. |
- gfx::ImageSkia tab_l = gfx::ImageSkiaOperations::CreateTiledImage( |
- *tab_bg, offset, bg_offset_y, tab_image->l_width, height()); |
- gfx::ImageSkia theme_l = |
- gfx::ImageSkiaOperations::CreateMaskedImage(tab_l, *alpha->image_l); |
- background_canvas.DrawImageInt(theme_l, |
- 0, 0, theme_l.width(), theme_l.height() - kToolbarOverlap, |
- 0, 0, theme_l.width(), theme_l.height() - kToolbarOverlap, |
- false); |
- |
- // Draw right edge. Again, don't draw over the toolbar. |
- gfx::ImageSkia tab_r = gfx::ImageSkiaOperations::CreateTiledImage(*tab_bg, |
- offset + width() - tab_image->r_width, bg_offset_y, |
- tab_image->r_width, height()); |
- gfx::ImageSkia theme_r = |
- gfx::ImageSkiaOperations::CreateMaskedImage(tab_r, *alpha->image_r); |
- background_canvas.DrawImageInt(theme_r, |
- 0, 0, theme_r.width(), theme_r.height() - kToolbarOverlap, |
- width() - theme_r.width(), 0, theme_r.width(), |
- theme_r.height() - kToolbarOverlap, false); |
- |
- // Draw center. Instead of masking out the top portion we simply skip over |
- // it by incrementing by GetDropShadowHeight(), since it's a simple |
- // rectangle. And again, don't draw over the toolbar. |
- background_canvas.TileImageInt(*tab_bg, |
- offset + tab_image->l_width, |
- bg_offset_y + kDropShadowHeight, |
- tab_image->l_width, |
- kDropShadowHeight, |
- width() - tab_image->l_width - tab_image->r_width, |
- height() - kDropShadowHeight - kToolbarOverlap); |
- |
- canvas->DrawImageInt( |
- gfx::ImageSkia(background_canvas.ExtractImageRep()), 0, 0); |
- |
- if (!GetThemeProvider()->HasCustomImage(tab_id) && |
- hover_controller_.ShouldDraw()) { |
- hover_controller_.Draw(canvas, gfx::ImageSkia( |
- background_canvas.ExtractImageRep())); |
- } |
- |
- // Now draw the highlights/shadows around the tab edge. |
- canvas->DrawImageInt(*tab_inactive_image->image_l, 0, 0); |
- canvas->TileImageInt(*tab_inactive_image->image_c, |
- tab_inactive_image->l_width, 0, |
- width() - tab_inactive_image->l_width - |
- tab_inactive_image->r_width, |
- height()); |
- canvas->DrawImageInt(*tab_inactive_image->image_r, |
- width() - tab_inactive_image->r_width, 0); |
-} |
- |
-void Tab::PaintActiveTabBackground(gfx::Canvas* canvas) { |
- gfx::ImageSkia* tab_background = |
- GetThemeProvider()->GetImageSkiaNamed(IDR_THEME_TOOLBAR); |
- int offset = GetMirroredX() + background_offset_.x(); |
- |
- TabImage* tab_image = &tab_active_; |
- TabImage* alpha = &tab_alpha_; |
- |
- // Draw left edge. |
- gfx::ImageSkia tab_l = gfx::ImageSkiaOperations::CreateTiledImage( |
- *tab_background, offset, 0, tab_image->l_width, height()); |
- gfx::ImageSkia theme_l = |
- gfx::ImageSkiaOperations::CreateMaskedImage(tab_l, *alpha->image_l); |
- canvas->DrawImageInt(theme_l, 0, 0); |
- |
- // Draw right edge. |
- gfx::ImageSkia tab_r = gfx::ImageSkiaOperations::CreateTiledImage( |
- *tab_background, |
- offset + width() - tab_image->r_width, 0, tab_image->r_width, height()); |
- gfx::ImageSkia theme_r = |
- gfx::ImageSkiaOperations::CreateMaskedImage(tab_r, *alpha->image_r); |
- canvas->DrawImageInt(theme_r, width() - tab_image->r_width, 0); |
- |
- // Draw center. Instead of masking out the top portion we simply skip over it |
- // by incrementing by GetDropShadowHeight(), since it's a simple rectangle. |
- canvas->TileImageInt(*tab_background, |
- offset + tab_image->l_width, |
- kDropShadowHeight, |
- tab_image->l_width, |
- kDropShadowHeight, |
- width() - tab_image->l_width - tab_image->r_width, |
- height() - kDropShadowHeight); |
- |
- // Now draw the highlights/shadows around the tab edge. |
- canvas->DrawImageInt(*tab_image->image_l, 0, 0); |
- canvas->TileImageInt(*tab_image->image_c, tab_image->l_width, 0, |
- width() - tab_image->l_width - tab_image->r_width, height()); |
- canvas->DrawImageInt(*tab_image->image_r, width() - tab_image->r_width, 0); |
-} |
- |
-void Tab::PaintIcon(gfx::Canvas* canvas) { |
- gfx::Rect bounds = GetIconBounds(); |
- if (bounds.IsEmpty()) |
- return; |
- |
- bounds.set_x(GetMirroredXForRect(bounds)); |
- |
- if (data().network_state != TabRendererData::NETWORK_STATE_NONE) { |
- // Paint network activity (aka throbber) animation frame. |
- ui::ThemeProvider* tp = GetThemeProvider(); |
- gfx::ImageSkia frames(*tp->GetImageSkiaNamed( |
- (data().network_state == TabRendererData::NETWORK_STATE_WAITING) ? |
- IDR_THROBBER_WAITING : IDR_THROBBER)); |
- |
- int icon_size = frames.height(); |
- int image_offset = loading_animation_frame_ * icon_size; |
- DrawIconCenter(canvas, frames, image_offset, |
- icon_size, icon_size, |
- bounds, false, SkPaint()); |
- } else if (should_display_crashed_favicon_) { |
- // Paint crash favicon. |
- ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
- gfx::ImageSkia crashed_favicon(*rb.GetImageSkiaNamed(IDR_SAD_FAVICON)); |
- bounds.set_y(bounds.y() + favicon_hiding_offset_); |
- DrawIconCenter(canvas, crashed_favicon, 0, |
- crashed_favicon.width(), |
- crashed_favicon.height(), |
- bounds, true, SkPaint()); |
- } else if (!data().favicon.isNull()) { |
- // Paint the normal favicon. |
- DrawIconCenter(canvas, data().favicon, 0, |
- data().favicon.width(), |
- data().favicon.height(), |
- bounds, true, SkPaint()); |
- } |
-} |
- |
-void Tab::PaintMediaIndicator(gfx::Canvas* canvas) { |
- if (media_indicator_bounds_.IsEmpty() || !media_indicator_animation_) |
- return; |
- |
- gfx::Rect bounds = media_indicator_bounds_; |
- bounds.set_x(GetMirroredXForRect(bounds)); |
- |
- SkPaint paint; |
- paint.setAntiAlias(true); |
- double opaqueness = media_indicator_animation_->GetCurrentValue(); |
- if (data_.media_state == TAB_MEDIA_STATE_NONE) |
- opaqueness = 1.0 - opaqueness; // Fading out, not in. |
- paint.setAlpha(opaqueness * SK_AlphaOPAQUE); |
- |
- const gfx::ImageSkia& media_indicator_image = |
- *(chrome::GetTabMediaIndicatorImage(animating_media_state_). |
- ToImageSkia()); |
- DrawIconAtLocation(canvas, media_indicator_image, 0, |
- bounds.x(), bounds.y(), media_indicator_image.width(), |
- media_indicator_image.height(), true, paint); |
-} |
- |
-void Tab::PaintTitle(gfx::Canvas* canvas, SkColor title_color) { |
- // Paint the Title. |
- base::string16 title = data().title; |
- if (title.empty()) { |
- title = data().loading ? |
- l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) : |
- CoreTabHelper::GetDefaultTitle(); |
- } else { |
- Browser::FormatTitleForDisplay(&title); |
- } |
- |
- canvas->DrawFadedString(title, gfx::FontList(*font_), title_color, |
- GetTitleBounds(), 0); |
-} |
- |
-void Tab::AdvanceLoadingAnimation(TabRendererData::NetworkState old_state, |
- TabRendererData::NetworkState state) { |
- static bool initialized = false; |
- static int loading_animation_frame_count = 0; |
- static int waiting_animation_frame_count = 0; |
- static int waiting_to_loading_frame_count_ratio = 0; |
- if (!initialized) { |
- initialized = true; |
- ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
- gfx::ImageSkia loading_animation(*rb.GetImageSkiaNamed(IDR_THROBBER)); |
- loading_animation_frame_count = |
- loading_animation.width() / loading_animation.height(); |
- gfx::ImageSkia waiting_animation(*rb.GetImageSkiaNamed( |
- IDR_THROBBER_WAITING)); |
- waiting_animation_frame_count = |
- waiting_animation.width() / waiting_animation.height(); |
- waiting_to_loading_frame_count_ratio = |
- waiting_animation_frame_count / loading_animation_frame_count; |
- |
- base::debug::Alias(&loading_animation_frame_count); |
- base::debug::Alias(&waiting_animation_frame_count); |
- CHECK_NE(0, waiting_to_loading_frame_count_ratio) << |
- "Number of frames in IDR_THROBBER must be equal to or greater " << |
- "than the number of frames in IDR_THROBBER_WAITING. Please " << |
- "investigate how this happened and update http://crbug.com/132590, " << |
- "this is causing crashes in the wild."; |
- } |
- |
- // The waiting animation is the reverse of the loading animation, but at a |
- // different rate - the following reverses and scales the animation_frame_ |
- // so that the frame is at an equivalent position when going from one |
- // animation to the other. |
- if (state != old_state) { |
- loading_animation_frame_ = loading_animation_frame_count - |
- (loading_animation_frame_ / waiting_to_loading_frame_count_ratio); |
- } |
- |
- if (state == TabRendererData::NETWORK_STATE_WAITING) { |
- loading_animation_frame_ = (loading_animation_frame_ + 1) % |
- waiting_animation_frame_count; |
- // Waiting steps backwards. |
- immersive_loading_step_ = |
- (immersive_loading_step_ - 1 + kImmersiveLoadingStepCount) % |
- kImmersiveLoadingStepCount; |
- } else if (state == TabRendererData::NETWORK_STATE_LOADING) { |
- loading_animation_frame_ = (loading_animation_frame_ + 1) % |
- loading_animation_frame_count; |
- immersive_loading_step_ = (immersive_loading_step_ + 1) % |
- kImmersiveLoadingStepCount; |
- } else { |
- loading_animation_frame_ = 0; |
- immersive_loading_step_ = 0; |
- } |
- if (controller_->IsImmersiveStyle()) |
- SchedulePaintInRect(GetImmersiveBarRect()); |
- else |
- ScheduleIconPaint(); |
-} |
- |
-int Tab::IconCapacity() const { |
- if (height() < GetMinimumUnselectedSize().height()) |
- return 0; |
- const int available_width = |
- std::max(0, width() - kLeftPadding - kRightPadding); |
- const int width_per_icon = gfx::kFaviconSize; |
- const int kPaddingBetweenIcons = 2; |
- if (available_width >= width_per_icon && |
- available_width < (width_per_icon + kPaddingBetweenIcons)) { |
- return 1; |
- } |
- return available_width / (width_per_icon + kPaddingBetweenIcons); |
-} |
- |
-bool Tab::ShouldShowIcon() const { |
- return chrome::ShouldTabShowFavicon( |
- IconCapacity(), data().mini, IsActive(), data().show_icon, |
- animating_media_state_); |
-} |
- |
-bool Tab::ShouldShowMediaIndicator() const { |
- return chrome::ShouldTabShowMediaIndicator( |
- IconCapacity(), data().mini, IsActive(), data().show_icon, |
- animating_media_state_); |
-} |
- |
-bool Tab::ShouldShowCloseBox() const { |
- return chrome::ShouldTabShowCloseButton( |
- IconCapacity(), data().mini, IsActive()); |
-} |
- |
-double Tab::GetThrobValue() { |
- bool is_selected = IsSelected(); |
- double min = is_selected ? kSelectedTabOpacity : 0; |
- double scale = is_selected ? kSelectedTabThrobScale : 1; |
- |
- if (!data().mini) { |
- if (tab_animation_.get() && tab_animation_->is_animating()) |
- return tab_animation_->GetCurrentValue() * kHoverOpacity * scale + min; |
- } |
- |
- if (hover_controller_.ShouldDraw()) { |
- return kHoverOpacity * hover_controller_.GetAnimationValue() * scale + |
- min; |
- } |
- |
- return is_selected ? kSelectedTabOpacity : 0; |
-} |
- |
-void Tab::SetFaviconHidingOffset(int offset) { |
- favicon_hiding_offset_ = offset; |
- ScheduleIconPaint(); |
-} |
- |
-void Tab::DisplayCrashedFavicon() { |
- should_display_crashed_favicon_ = true; |
-} |
- |
-void Tab::ResetCrashedFavicon() { |
- should_display_crashed_favicon_ = false; |
-} |
- |
-void Tab::StopCrashAnimation() { |
- crash_icon_animation_.reset(); |
-} |
- |
-void Tab::StartCrashAnimation() { |
- crash_icon_animation_.reset(new FaviconCrashAnimation(this)); |
- crash_icon_animation_->Start(); |
-} |
- |
-bool Tab::IsPerformingCrashAnimation() const { |
- return crash_icon_animation_.get() && data_.IsCrashed(); |
-} |
- |
-void Tab::StartMediaIndicatorAnimation() { |
- media_indicator_animation_ = |
- chrome::CreateTabMediaIndicatorFadeAnimation(data_.media_state); |
- media_indicator_animation_->set_delegate(this); |
- media_indicator_animation_->Start(); |
-} |
- |
-void Tab::ScheduleIconPaint() { |
- gfx::Rect bounds = GetIconBounds(); |
- if (bounds.IsEmpty()) |
- return; |
- |
- // Extends the area to the bottom when sad_favicon is |
- // animating. |
- if (IsPerformingCrashAnimation()) |
- bounds.set_height(height() - bounds.y()); |
- bounds.set_x(GetMirroredXForRect(bounds)); |
- SchedulePaintInRect(bounds); |
-} |
- |
-gfx::Rect Tab::GetImmersiveBarRect() const { |
- // The main bar is as wide as the normal tab's horizontal top line. |
- // This top line of the tab extends a few pixels left and right of the |
- // center image due to pixels in the rounded corner images. |
- const int kBarPadding = 1; |
- int main_bar_left = tab_active_.l_width - kBarPadding; |
- int main_bar_right = width() - tab_active_.r_width + kBarPadding; |
- return gfx::Rect( |
- main_bar_left, 0, main_bar_right - main_bar_left, kImmersiveBarHeight); |
-} |
- |
-void Tab::GetTabIdAndFrameId(views::Widget* widget, |
- int* tab_id, |
- int* frame_id) const { |
- if (widget && |
- widget->GetTopLevelWidget()->ShouldWindowContentsBeTransparent()) { |
- *tab_id = IDR_THEME_TAB_BACKGROUND_V; |
- *frame_id = 0; |
- } else if (data().incognito) { |
- *tab_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO; |
- *frame_id = IDR_THEME_FRAME_INCOGNITO; |
- } else { |
- *tab_id = IDR_THEME_TAB_BACKGROUND; |
- *frame_id = IDR_THEME_FRAME; |
- } |
-} |
- |
-//////////////////////////////////////////////////////////////////////////////// |
-// Tab, private static: |
- |
-// static |
-void Tab::InitTabResources() { |
- static bool initialized = false; |
- if (initialized) |
- return; |
- |
- initialized = true; |
- |
- ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
- font_ = new gfx::Font(rb.GetFont(ui::ResourceBundle::BaseFont)); |
- font_height_ = font_->GetHeight(); |
- |
- image_cache_ = new ImageCache(); |
- |
- // Load the tab images once now, and maybe again later if the theme changes. |
- LoadTabImages(); |
-} |
- |
-// static |
-void Tab::LoadTabImages() { |
- // We're not letting people override tab images just yet. |
- ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
- |
- tab_alpha_.image_l = rb.GetImageSkiaNamed(IDR_TAB_ALPHA_LEFT); |
- tab_alpha_.image_r = rb.GetImageSkiaNamed(IDR_TAB_ALPHA_RIGHT); |
- |
- tab_active_.image_l = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_LEFT); |
- tab_active_.image_c = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_CENTER); |
- tab_active_.image_r = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_RIGHT); |
- tab_active_.l_width = tab_active_.image_l->width(); |
- tab_active_.r_width = tab_active_.image_r->width(); |
- |
- tab_inactive_.image_l = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_LEFT); |
- tab_inactive_.image_c = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_CENTER); |
- tab_inactive_.image_r = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_RIGHT); |
- tab_inactive_.l_width = tab_inactive_.image_l->width(); |
- tab_inactive_.r_width = tab_inactive_.image_r->width(); |
-} |
- |
-// static |
-gfx::ImageSkia Tab::GetCachedImage(int resource_id, |
- const gfx::Size& size, |
- ui::ScaleFactor scale_factor) { |
- for (ImageCache::const_iterator i = image_cache_->begin(); |
- i != image_cache_->end(); ++i) { |
- if (i->resource_id == resource_id && i->scale_factor == scale_factor && |
- i->image.size() == size) { |
- return i->image; |
- } |
- } |
- return gfx::ImageSkia(); |
-} |
- |
-// static |
-void Tab::SetCachedImage(int resource_id, |
- ui::ScaleFactor scale_factor, |
- const gfx::ImageSkia& image) { |
- DCHECK_NE(scale_factor, ui::SCALE_FACTOR_NONE); |
- ImageCacheEntry entry; |
- entry.resource_id = resource_id; |
- entry.scale_factor = scale_factor; |
- entry.image = image; |
- image_cache_->push_front(entry); |
- if (image_cache_->size() > kMaxImageCacheSize) |
- image_cache_->pop_back(); |
-} |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chrome/browser/ui/views/tabs/tab.h" |
+ |
+#include <limits> |
+ |
+#include "base/command_line.h" |
+#include "base/debug/alias.h" |
+#include "base/strings/utf_string_conversions.h" |
+#include "chrome/browser/defaults.h" |
+#include "chrome/browser/themes/theme_properties.h" |
+#include "chrome/browser/ui/browser.h" |
+#include "chrome/browser/ui/tab_contents/core_tab_helper.h" |
+#include "chrome/browser/ui/tabs/tab_resources.h" |
+#include "chrome/browser/ui/tabs/tab_utils.h" |
+#include "chrome/browser/ui/view_ids.h" |
+#include "chrome/browser/ui/views/tabs/tab_controller.h" |
+#include "chrome/browser/ui/views/theme_image_mapper.h" |
+#include "chrome/browser/ui/views/touch_uma/touch_uma.h" |
+#include "chrome/common/chrome_switches.h" |
+#include "grit/generated_resources.h" |
+#include "grit/theme_resources.h" |
+#include "grit/ui_resources.h" |
+#include "third_party/skia/include/effects/SkGradientShader.h" |
+#include "ui/accessibility/ax_view_state.h" |
+#include "ui/base/l10n/l10n_util.h" |
+#include "ui/base/models/list_selection_model.h" |
+#include "ui/base/resource/resource_bundle.h" |
+#include "ui/base/theme_provider.h" |
+#include "ui/gfx/animation/animation_container.h" |
+#include "ui/gfx/animation/multi_animation.h" |
+#include "ui/gfx/animation/throb_animation.h" |
+#include "ui/gfx/canvas.h" |
+#include "ui/gfx/color_analysis.h" |
+#include "ui/gfx/favicon_size.h" |
+#include "ui/gfx/image/image_skia_operations.h" |
+#include "ui/gfx/path.h" |
+#include "ui/gfx/rect_conversions.h" |
+#include "ui/gfx/skia_util.h" |
+#include "ui/gfx/text_elider.h" |
+#include "ui/views/border.h" |
+#include "ui/views/controls/button/image_button.h" |
+#include "ui/views/controls/label.h" |
+#include "ui/views/rect_based_targeting_utils.h" |
+#include "ui/views/widget/tooltip_manager.h" |
+#include "ui/views/widget/widget.h" |
+#include "ui/views/window/non_client_view.h" |
+ |
+#if defined(USE_ASH) |
+#include "ui/aura/env.h" |
+#endif |
+ |
+namespace { |
+ |
+// Padding around the "content" of a tab, occupied by the tab border graphics. |
+const int kLeftPadding = 22; |
+const int kTopPadding = 7; |
+const int kRightPadding = 17; |
+const int kBottomPadding = 5; |
+ |
+// Height of the shadow at the top of the tab image assets. |
+const int kDropShadowHeight = 4; |
+ |
+// How long the pulse throb takes. |
+const int kPulseDurationMs = 200; |
+ |
+// Width of touch tabs. |
+static const int kTouchWidth = 120; |
+ |
+static const int kToolbarOverlap = 1; |
+static const int kFaviconTitleSpacing = 4; |
+// Additional vertical offset for title text relative to top of tab. |
+// Ash text rendering may be different than Windows. |
+static const int kTitleTextOffsetYAsh = 1; |
+static const int kTitleTextOffsetY = 0; |
+static const int kTitleCloseButtonSpacing = 3; |
+static const int kStandardTitleWidth = 175; |
+// Additional vertical offset for close button relative to top of tab. |
+// Ash needs this to match the text vertical position. |
+static const int kCloseButtonVertFuzzAsh = 1; |
+static const int kCloseButtonVertFuzz = 0; |
+// Additional horizontal offset for close button relative to title text. |
+static const int kCloseButtonHorzFuzz = 3; |
+ |
+// When a non-mini-tab becomes a mini-tab the width of the tab animates. If |
+// the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab |
+// is rendered as a normal tab. This is done to avoid having the title |
+// immediately disappear when transitioning a tab from normal to mini-tab. |
+static const int kMiniTabRendererAsNormalTabWidth = |
+ browser_defaults::kMiniTabWidth + 30; |
+ |
+// How opaque to make the hover state (out of 1). |
+static const double kHoverOpacity = 0.33; |
+ |
+// Opacity for non-active selected tabs. |
+static const double kSelectedTabOpacity = .45; |
+ |
+// Selected (but not active) tabs have their throb value scaled down by this. |
+static const double kSelectedTabThrobScale = .5; |
+ |
+// Durations for the various parts of the mini tab title animation. |
+static const int kMiniTitleChangeAnimationDuration1MS = 1600; |
+static const int kMiniTitleChangeAnimationStart1MS = 0; |
+static const int kMiniTitleChangeAnimationEnd1MS = 1900; |
+static const int kMiniTitleChangeAnimationDuration2MS = 0; |
+static const int kMiniTitleChangeAnimationDuration3MS = 550; |
+static const int kMiniTitleChangeAnimationStart3MS = 150; |
+static const int kMiniTitleChangeAnimationEnd3MS = 800; |
+static const int kMiniTitleChangeAnimationIntervalMS = 40; |
+ |
+// Offset from the right edge for the start of the mini title change animation. |
+static const int kMiniTitleChangeInitialXOffset = 6; |
+ |
+// Radius of the radial gradient used for mini title change animation. |
+static const int kMiniTitleChangeGradientRadius = 20; |
+ |
+// Colors of the gradient used during the mini title change animation. |
+static const SkColor kMiniTitleChangeGradientColor1 = SK_ColorWHITE; |
+static const SkColor kMiniTitleChangeGradientColor2 = |
+ SkColorSetARGB(0, 255, 255, 255); |
+ |
+// Max number of images to cache. This has to be at least two since rounding |
+// errors may lead to tabs in the same tabstrip having different sizes. |
+const size_t kMaxImageCacheSize = 4; |
+ |
+// Height of the miniature tab strip in immersive mode. |
+const int kImmersiveTabHeight = 3; |
+ |
+// Height of the small tab indicator rectangles in immersive mode. |
+const int kImmersiveBarHeight = 2; |
+ |
+// Color for active and inactive tabs in the immersive mode light strip. These |
+// should be a little brighter than the color of the normal art assets for tabs, |
+// which for active tabs is 230, 230, 230 and for inactive is 184, 184, 184. |
+const SkColor kImmersiveActiveTabColor = SkColorSetRGB(235, 235, 235); |
+const SkColor kImmersiveInactiveTabColor = SkColorSetRGB(190, 190, 190); |
+ |
+// The minimum opacity (out of 1) when a tab (either active or inactive) is |
+// throbbing in the immersive mode light strip. |
+const double kImmersiveTabMinThrobOpacity = 0.66; |
+ |
+// Number of steps in the immersive mode loading animation. |
+const int kImmersiveLoadingStepCount = 32; |
+ |
+const char kTabCloseButtonName[] = "TabCloseButton"; |
+ |
+void DrawIconAtLocation(gfx::Canvas* canvas, |
+ const gfx::ImageSkia& image, |
+ int image_offset, |
+ int dst_x, |
+ int dst_y, |
+ int icon_width, |
+ int icon_height, |
+ bool filter, |
+ const SkPaint& paint) { |
+ // NOTE: the clipping is a work around for 69528, it shouldn't be necessary. |
+ canvas->Save(); |
+ canvas->ClipRect(gfx::Rect(dst_x, dst_y, icon_width, icon_height)); |
+ canvas->DrawImageInt(image, |
+ image_offset, 0, icon_width, icon_height, |
+ dst_x, dst_y, icon_width, icon_height, |
+ filter, paint); |
+ canvas->Restore(); |
+} |
+ |
+// Draws the icon image at the center of |bounds|. |
+void DrawIconCenter(gfx::Canvas* canvas, |
+ const gfx::ImageSkia& image, |
+ int image_offset, |
+ int icon_width, |
+ int icon_height, |
+ const gfx::Rect& bounds, |
+ bool filter, |
+ const SkPaint& paint) { |
+ // Center the image within bounds. |
+ int dst_x = bounds.x() - (icon_width - bounds.width()) / 2; |
+ int dst_y = bounds.y() - (icon_height - bounds.height()) / 2; |
+ DrawIconAtLocation(canvas, image, image_offset, dst_x, dst_y, icon_width, |
+ icon_height, filter, paint); |
+} |
+ |
+chrome::HostDesktopType GetHostDesktopType(views::View* view) { |
+ // Widget is NULL when tabs are detached. |
+ views::Widget* widget = view->GetWidget(); |
+ return chrome::GetHostDesktopTypeForNativeView( |
+ widget ? widget->GetNativeView() : NULL); |
+} |
+ |
+} // namespace |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// FaviconCrashAnimation |
+// |
+// A custom animation subclass to manage the favicon crash animation. |
+class Tab::FaviconCrashAnimation : public gfx::LinearAnimation, |
+ public gfx::AnimationDelegate { |
+ public: |
+ explicit FaviconCrashAnimation(Tab* target) |
+ : gfx::LinearAnimation(1000, 25, this), |
+ target_(target) { |
+ } |
+ virtual ~FaviconCrashAnimation() {} |
+ |
+ // gfx::Animation overrides: |
+ virtual void AnimateToState(double state) OVERRIDE { |
+ const double kHidingOffset = 27; |
+ |
+ if (state < .5) { |
+ target_->SetFaviconHidingOffset( |
+ static_cast<int>(floor(kHidingOffset * 2.0 * state))); |
+ } else { |
+ target_->DisplayCrashedFavicon(); |
+ target_->SetFaviconHidingOffset( |
+ static_cast<int>( |
+ floor(kHidingOffset - ((state - .5) * 2.0 * kHidingOffset)))); |
+ } |
+ } |
+ |
+ // gfx::AnimationDelegate overrides: |
+ virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE { |
+ target_->SetFaviconHidingOffset(0); |
+ } |
+ |
+ private: |
+ Tab* target_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation); |
+}; |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// TabCloseButton |
+// |
+// This is a Button subclass that causes middle clicks to be forwarded to the |
+// parent View by explicitly not handling them in OnMousePressed. |
+class Tab::TabCloseButton : public views::ImageButton { |
+ public: |
+ explicit TabCloseButton(Tab* tab) : views::ImageButton(tab), tab_(tab) {} |
+ virtual ~TabCloseButton() {} |
+ |
+ // Overridden from views::View. |
+ virtual View* GetEventHandlerForRect(const gfx::Rect& rect) OVERRIDE { |
+ if (!views::UsePointBasedTargeting(rect)) |
+ return View::GetEventHandlerForRect(rect); |
+ |
+ // Ignore the padding set on the button. |
+ gfx::Rect contents_bounds = GetContentsBounds(); |
+ contents_bounds.set_x(GetMirroredXForRect(contents_bounds)); |
+ |
+ // TODO(tdanderson): Remove this ifdef if rect-based targeting |
+ // is turned on by default. |
+#if defined(USE_ASH) |
+ // Include the padding in hit-test for touch events. |
+ if (aura::Env::GetInstance()->is_touch_down()) |
+ contents_bounds = GetLocalBounds(); |
+#elif defined(OS_WIN) |
+ // TODO(sky): Use local-bounds if a touch-point is active. |
+ // http://crbug.com/145258 |
+#endif |
+ |
+ return contents_bounds.Intersects(rect) ? this : parent(); |
+ } |
+ |
+ // Overridden from views::View. |
+ virtual View* GetTooltipHandlerForPoint(const gfx::Point& point) OVERRIDE { |
+ // Tab close button has no children, so tooltip handler should be the same |
+ // as the event handler. |
+ // In addition, a hit test has to be performed for the point (as |
+ // GetTooltipHandlerForPoint() is responsible for it). |
+ if (!HitTestPoint(point)) |
+ return NULL; |
+ return GetEventHandlerForPoint(point); |
+ } |
+ |
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE { |
+ tab_->controller_->OnMouseEventInTab(this, event); |
+ |
+ bool handled = ImageButton::OnMousePressed(event); |
+ // Explicitly mark midle-mouse clicks as non-handled to ensure the tab |
+ // sees them. |
+ return event.IsOnlyMiddleMouseButton() ? false : handled; |
+ } |
+ |
+ virtual void OnMouseMoved(const ui::MouseEvent& event) OVERRIDE { |
+ tab_->controller_->OnMouseEventInTab(this, event); |
+ CustomButton::OnMouseMoved(event); |
+ } |
+ |
+ virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE { |
+ tab_->controller_->OnMouseEventInTab(this, event); |
+ CustomButton::OnMouseReleased(event); |
+ } |
+ |
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { |
+ // Consume all gesture events here so that the parent (Tab) does not |
+ // start consuming gestures. |
+ ImageButton::OnGestureEvent(event); |
+ event->SetHandled(); |
+ } |
+ |
+ virtual bool HasHitTestMask() const OVERRIDE { |
+ return true; |
+ } |
+ |
+ virtual void GetHitTestMask(HitTestSource source, |
+ gfx::Path* path) const OVERRIDE { |
+ // Use the button's contents bounds (which does not include padding) |
+ // and the hit test mask of our parent |tab_| to determine if the |
+ // button is hidden behind another tab. |
+ gfx::Path tab_mask; |
+ tab_->GetHitTestMask(source, &tab_mask); |
+ |
+ gfx::Rect button_bounds(GetContentsBounds()); |
+ button_bounds.set_x(GetMirroredXForRect(button_bounds)); |
+ gfx::RectF tab_bounds_f(gfx::SkRectToRectF(tab_mask.getBounds())); |
+ views::View::ConvertRectToTarget(tab_, this, &tab_bounds_f); |
+ gfx::Rect tab_bounds = gfx::ToEnclosingRect(tab_bounds_f); |
+ |
+ // If either the top or bottom of the tab close button is clipped, |
+ // do not consider these regions to be part of the button's bounds. |
+ int top_overflow = tab_bounds.y() - button_bounds.y(); |
+ int bottom_overflow = button_bounds.bottom() - tab_bounds.bottom(); |
+ if (top_overflow > 0) |
+ button_bounds.set_y(tab_bounds.y()); |
+ else if (bottom_overflow > 0) |
+ button_bounds.set_height(button_bounds.height() - bottom_overflow); |
+ |
+ // If the hit test request is in response to a gesture, |path| should be |
+ // empty unless the entire tab close button is visible to the user. Hit |
+ // test requests in response to a mouse event should always set |path| |
+ // to be the visible portion of the tab close button, even if it is |
+ // partially hidden behind another tab. |
+ path->reset(); |
+ gfx::Rect intersection(gfx::IntersectRects(tab_bounds, button_bounds)); |
+ if (!intersection.IsEmpty()) { |
+ // TODO(tdanderson): Consider always returning the intersection if |
+ // the non-rectangular shape of the tabs can be accounted for. |
+ if (source == HIT_TEST_SOURCE_TOUCH && |
+ !tab_bounds.Contains(button_bounds)) |
+ return; |
+ |
+ path->addRect(RectToSkRect(intersection)); |
+ } |
+ } |
+ |
+ virtual const char* GetClassName() const OVERRIDE { |
+ return kTabCloseButtonName; |
+ } |
+ |
+ private: |
+ Tab* tab_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TabCloseButton); |
+}; |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// ImageCacheEntry |
+ |
+Tab::ImageCacheEntry::ImageCacheEntry() |
+ : resource_id(-1), |
+ scale_factor(ui::SCALE_FACTOR_NONE) { |
+} |
+ |
+Tab::ImageCacheEntry::~ImageCacheEntry() {} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// Tab, statics: |
+ |
+// static |
+const char Tab::kViewClassName[] = "Tab"; |
+ |
+// static |
+Tab::TabImage Tab::tab_alpha_ = {0}; |
+Tab::TabImage Tab::tab_active_ = {0}; |
+Tab::TabImage Tab::tab_inactive_ = {0}; |
+// static |
+Tab::ImageCache* Tab::image_cache_ = NULL; |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// Tab, public: |
+ |
+Tab::Tab(TabController* controller) |
+ : controller_(controller), |
+ closing_(false), |
+ dragging_(false), |
+ favicon_hiding_offset_(0), |
+ loading_animation_frame_(0), |
+ immersive_loading_step_(0), |
+ should_display_crashed_favicon_(false), |
+ animating_media_state_(TAB_MEDIA_STATE_NONE), |
+ close_button_(NULL), |
+ title_(new views::Label()), |
+ tab_activated_with_last_gesture_begin_(false), |
+ hover_controller_(this), |
+ showing_icon_(false), |
+ showing_media_indicator_(false), |
+ showing_close_button_(false), |
+ close_button_color_(0) { |
+ DCHECK(controller); |
+ InitTabResources(); |
+ |
+ // So we get don't get enter/exit on children and don't prematurely stop the |
+ // hover. |
+ set_notify_enter_exit_on_child(true); |
+ |
+ set_id(VIEW_ID_TAB); |
+ |
+ title_->set_directionality_mode(gfx::DIRECTIONALITY_FROM_TEXT); |
+ title_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD); |
+ title_->SetElideBehavior(gfx::FADE_TAIL); |
+ AddChildView(title_); |
+ |
+ // Add the Close Button. |
+ close_button_ = new TabCloseButton(this); |
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
+ close_button_->SetImage(views::CustomButton::STATE_NORMAL, |
+ rb.GetImageSkiaNamed(IDR_CLOSE_1)); |
+ close_button_->SetImage(views::CustomButton::STATE_HOVERED, |
+ rb.GetImageSkiaNamed(IDR_CLOSE_1_H)); |
+ close_button_->SetImage(views::CustomButton::STATE_PRESSED, |
+ rb.GetImageSkiaNamed(IDR_CLOSE_1_P)); |
+ close_button_->SetAccessibleName( |
+ l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE)); |
+ // Disable animation so that the red danger sign shows up immediately |
+ // to help avoid mis-clicks. |
+ close_button_->SetAnimationDuration(0); |
+ AddChildView(close_button_); |
+ |
+ set_context_menu_controller(this); |
+} |
+ |
+Tab::~Tab() { |
+} |
+ |
+void Tab::set_animation_container(gfx::AnimationContainer* container) { |
+ animation_container_ = container; |
+ hover_controller_.SetAnimationContainer(container); |
+} |
+ |
+bool Tab::IsActive() const { |
+ return controller_->IsActiveTab(this); |
+} |
+ |
+bool Tab::IsSelected() const { |
+ return controller_->IsTabSelected(this); |
+} |
+ |
+void Tab::SetData(const TabRendererData& data) { |
+ if (data_.Equals(data)) |
+ return; |
+ |
+ TabRendererData old(data_); |
+ data_ = data; |
+ |
+ base::string16 title = data_.title; |
+ if (title.empty()) { |
+ title = data_.loading ? |
+ l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) : |
+ CoreTabHelper::GetDefaultTitle(); |
+ } else { |
+ Browser::FormatTitleForDisplay(&title); |
+ } |
+ title_->SetText(title); |
+ |
+ if (data_.IsCrashed()) { |
+ if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation()) { |
+ data_.media_state = TAB_MEDIA_STATE_NONE; |
+#if defined(OS_CHROMEOS) |
+ // On Chrome OS, we reload killed tabs automatically when the user |
+ // switches to them. Don't display animations for these unless they're |
+ // selected (i.e. in the foreground) -- we won't reload these |
+ // automatically since we don't want to get into a crash loop. |
+ if (IsSelected() || |
+ data_.crashed_status != base::TERMINATION_STATUS_PROCESS_WAS_KILLED) |
+ StartCrashAnimation(); |
+#else |
+ StartCrashAnimation(); |
+#endif |
+ } |
+ } else { |
+ if (IsPerformingCrashAnimation()) |
+ StopCrashAnimation(); |
+ ResetCrashedFavicon(); |
+ } |
+ |
+ if (data_.media_state != old.media_state) { |
+ if (data_.media_state != TAB_MEDIA_STATE_NONE) |
+ animating_media_state_ = data_.media_state; |
+ StartMediaIndicatorAnimation(); |
+ } |
+ |
+ if (old.mini != data_.mini) { |
+ if (tab_animation_.get() && tab_animation_->is_animating()) { |
+ tab_animation_->Stop(); |
+ tab_animation_.reset(NULL); |
+ } |
+ } |
+ |
+ DataChanged(old); |
+ |
+ Layout(); |
+ SchedulePaint(); |
+} |
+ |
+void Tab::UpdateLoadingAnimation(TabRendererData::NetworkState state) { |
+ if (state == data_.network_state && |
+ state == TabRendererData::NETWORK_STATE_NONE) { |
+ // If the network state is none and hasn't changed, do nothing. Otherwise we |
+ // need to advance the animation frame. |
+ return; |
+ } |
+ |
+ TabRendererData::NetworkState old_state = data_.network_state; |
+ data_.network_state = state; |
+ AdvanceLoadingAnimation(old_state, state); |
+} |
+ |
+void Tab::StartPulse() { |
+ gfx::ThrobAnimation* animation = new gfx::ThrobAnimation(this); |
+ animation->SetSlideDuration(kPulseDurationMs); |
+ if (animation_container_.get()) |
+ animation->SetContainer(animation_container_.get()); |
+ animation->StartThrobbing(std::numeric_limits<int>::max()); |
+ tab_animation_.reset(animation); |
+} |
+ |
+void Tab::StopPulse() { |
+ if (!tab_animation_.get()) |
+ return; |
+ tab_animation_->Stop(); |
+ tab_animation_.reset(NULL); |
+} |
+ |
+void Tab::StartMiniTabTitleAnimation() { |
+ // We can only do this animation if the tab is mini because we will |
+ // upcast tab_animation back to MultiAnimation when we draw. |
+ if (!data().mini) |
+ return; |
+ if (!tab_animation_.get()) { |
+ gfx::MultiAnimation::Parts parts; |
+ parts.push_back( |
+ gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration1MS, |
+ gfx::Tween::EASE_OUT)); |
+ parts.push_back( |
+ gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration2MS, |
+ gfx::Tween::ZERO)); |
+ parts.push_back( |
+ gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration3MS, |
+ gfx::Tween::EASE_IN)); |
+ parts[0].start_time_ms = kMiniTitleChangeAnimationStart1MS; |
+ parts[0].end_time_ms = kMiniTitleChangeAnimationEnd1MS; |
+ parts[2].start_time_ms = kMiniTitleChangeAnimationStart3MS; |
+ parts[2].end_time_ms = kMiniTitleChangeAnimationEnd3MS; |
+ base::TimeDelta timeout = |
+ base::TimeDelta::FromMilliseconds(kMiniTitleChangeAnimationIntervalMS); |
+ gfx::MultiAnimation* animation = new gfx::MultiAnimation(parts, timeout); |
+ if (animation_container_.get()) |
+ animation->SetContainer(animation_container_.get()); |
+ animation->set_delegate(this); |
+ tab_animation_.reset(animation); |
+ } |
+ tab_animation_->Start(); |
+} |
+ |
+void Tab::StopMiniTabTitleAnimation() { |
+ if (!tab_animation_.get()) |
+ return; |
+ tab_animation_->Stop(); |
+ tab_animation_.reset(NULL); |
+} |
+ |
+// static |
+gfx::Size Tab::GetBasicMinimumUnselectedSize() { |
+ InitTabResources(); |
+ |
+ gfx::Size minimum_size; |
+ minimum_size.set_width(kLeftPadding + kRightPadding); |
+ // Since we use image images, the real minimum height of the image is |
+ // defined most accurately by the height of the end cap images. |
+ minimum_size.set_height(tab_active_.image_l->height()); |
+ return minimum_size; |
+} |
+ |
+gfx::Size Tab::GetMinimumUnselectedSize() { |
+ return GetBasicMinimumUnselectedSize(); |
+} |
+ |
+// static |
+gfx::Size Tab::GetMinimumSelectedSize() { |
+ gfx::Size minimum_size = GetBasicMinimumUnselectedSize(); |
+ minimum_size.set_width( |
+ kLeftPadding + gfx::kFaviconSize + kRightPadding); |
+ return minimum_size; |
+} |
+ |
+// static |
+gfx::Size Tab::GetStandardSize() { |
+ gfx::Size standard_size = GetBasicMinimumUnselectedSize(); |
+ standard_size.set_width( |
+ standard_size.width() + kFaviconTitleSpacing + kStandardTitleWidth); |
+ return standard_size; |
+} |
+ |
+// static |
+int Tab::GetTouchWidth() { |
+ return kTouchWidth; |
+} |
+ |
+// static |
+int Tab::GetMiniWidth() { |
+ return browser_defaults::kMiniTabWidth; |
+} |
+ |
+// static |
+int Tab::GetImmersiveHeight() { |
+ return kImmersiveTabHeight; |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// Tab, AnimationDelegate overrides: |
+ |
+void Tab::AnimationProgressed(const gfx::Animation* animation) { |
+ // Ignore if the pulse animation is being performed on active tab because |
+ // it repaints the same image. See |Tab::PaintTabBackground()|. |
+ if (animation == tab_animation_.get() && IsActive()) |
+ return; |
+ SchedulePaint(); |
+} |
+ |
+void Tab::AnimationCanceled(const gfx::Animation* animation) { |
+ if (media_indicator_animation_ == animation) |
+ animating_media_state_ = data_.media_state; |
+ SchedulePaint(); |
+} |
+ |
+void Tab::AnimationEnded(const gfx::Animation* animation) { |
+ if (media_indicator_animation_ == animation) |
+ animating_media_state_ = data_.media_state; |
+ SchedulePaint(); |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// Tab, views::ButtonListener overrides: |
+ |
+void Tab::ButtonPressed(views::Button* sender, const ui::Event& event) { |
+ const CloseTabSource source = |
+ (event.type() == ui::ET_MOUSE_RELEASED && |
+ (event.flags() & ui::EF_FROM_TOUCH) == 0) ? CLOSE_TAB_FROM_MOUSE : |
+ CLOSE_TAB_FROM_TOUCH; |
+ DCHECK_EQ(close_button_, sender); |
+ controller_->CloseTab(this, source); |
+ if (event.type() == ui::ET_GESTURE_TAP) |
+ TouchUMA::RecordGestureAction(TouchUMA::GESTURE_TABCLOSE_TAP); |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// Tab, views::ContextMenuController overrides: |
+ |
+void Tab::ShowContextMenuForView(views::View* source, |
+ const gfx::Point& point, |
+ ui::MenuSourceType source_type) { |
+ if (!closing()) |
+ controller_->ShowContextMenuForTab(this, point, source_type); |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// Tab, views::View overrides: |
+ |
+void Tab::OnPaint(gfx::Canvas* canvas) { |
+ // Don't paint if we're narrower than we can render correctly. (This should |
+ // only happen during animations). |
+ if (width() < GetMinimumUnselectedSize().width() && !data().mini) |
+ return; |
+ |
+ gfx::Rect clip; |
+ if (!controller_->ShouldPaintTab(this, &clip)) |
+ return; |
+ if (!clip.IsEmpty()) { |
+ canvas->Save(); |
+ canvas->ClipRect(clip); |
+ } |
+ |
+ if (controller_->IsImmersiveStyle()) |
+ PaintImmersiveTab(canvas); |
+ else |
+ PaintTab(canvas); |
+ |
+ if (!clip.IsEmpty()) |
+ canvas->Restore(); |
+} |
+ |
+void Tab::Layout() { |
+ gfx::Rect lb = GetContentsBounds(); |
+ if (lb.IsEmpty()) |
+ return; |
+ lb.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding); |
+ |
+ // The height of the content of the Tab is the largest of the favicon, |
+ // the title text and the close button graphic. |
+ const int kTabIconSize = gfx::kFaviconSize; |
+ const int font_height = title_->font_list().GetHeight(); |
+ int content_height = std::max(kTabIconSize, font_height); |
+ close_button_->SetBorder(views::Border::NullBorder()); |
+ gfx::Size close_button_size(close_button_->GetPreferredSize()); |
+ content_height = std::max(content_height, close_button_size.height()); |
+ |
+ // Size the Favicon. |
+ showing_icon_ = ShouldShowIcon(); |
+ if (showing_icon_) { |
+ // Use the size of the favicon as apps use a bigger favicon size. |
+ int favicon_top = kTopPadding + content_height / 2 - kTabIconSize / 2; |
+ int favicon_left = lb.x(); |
+ favicon_bounds_.SetRect(favicon_left, favicon_top, |
+ kTabIconSize, kTabIconSize); |
+ MaybeAdjustLeftForMiniTab(&favicon_bounds_); |
+ } else { |
+ favicon_bounds_.SetRect(lb.x(), lb.y(), 0, 0); |
+ } |
+ |
+ // Size the Close button. |
+ showing_close_button_ = ShouldShowCloseBox(); |
+ const bool is_host_desktop_type_ash = |
+ GetHostDesktopType(this) == chrome::HOST_DESKTOP_TYPE_ASH; |
+ if (showing_close_button_) { |
+ const int close_button_vert_fuzz = is_host_desktop_type_ash ? |
+ kCloseButtonVertFuzzAsh : kCloseButtonVertFuzz; |
+ int close_button_top = kTopPadding + close_button_vert_fuzz + |
+ (content_height - close_button_size.height()) / 2; |
+ // If the ratio of the close button size to tab width exceeds the maximum. |
+ // The close button should be as large as possible so that there is a larger |
+ // hit-target for touch events. So the close button bounds extends to the |
+ // edges of the tab. However, the larger hit-target should be active only |
+ // for mouse events, and the close-image should show up in the right place. |
+ // So a border is added to the button with necessary padding. The close |
+ // button (BaseTab::TabCloseButton) makes sure the padding is a hit-target |
+ // only for touch events. |
+ int top_border = close_button_top; |
+ int bottom_border = height() - (close_button_size.height() + top_border); |
+ int left_border = kCloseButtonHorzFuzz; |
+ int right_border = width() - (lb.width() + close_button_size.width() + |
+ left_border); |
+ close_button_->SetBorder(views::Border::CreateEmptyBorder( |
+ top_border, left_border, bottom_border, right_border)); |
+ close_button_->SetPosition(gfx::Point(lb.width(), 0)); |
+ close_button_->SizeToPreferredSize(); |
+ close_button_->SetVisible(true); |
+ } else { |
+ close_button_->SetBounds(0, 0, 0, 0); |
+ close_button_->SetVisible(false); |
+ } |
+ |
+ showing_media_indicator_ = ShouldShowMediaIndicator(); |
+ if (showing_media_indicator_) { |
+ const gfx::Image& media_indicator_image = |
+ chrome::GetTabMediaIndicatorImage(animating_media_state_); |
+ media_indicator_bounds_.set_width(media_indicator_image.Width()); |
+ media_indicator_bounds_.set_height(media_indicator_image.Height()); |
+ media_indicator_bounds_.set_y( |
+ kTopPadding + |
+ (content_height - media_indicator_bounds_.height()) / 2); |
+ const int right = showing_close_button_ ? |
+ close_button_->x() + close_button_->GetInsets().left() : lb.right(); |
+ media_indicator_bounds_.set_x( |
+ std::max(lb.x(), right - media_indicator_bounds_.width())); |
+ MaybeAdjustLeftForMiniTab(&media_indicator_bounds_); |
+ } else { |
+ media_indicator_bounds_.SetRect(lb.x(), lb.y(), 0, 0); |
+ } |
+ |
+ const int title_text_offset = is_host_desktop_type_ash ? |
+ kTitleTextOffsetYAsh : kTitleTextOffsetY; |
+ int title_left = favicon_bounds_.right() + kFaviconTitleSpacing; |
+ int title_top = kTopPadding + title_text_offset + |
+ (content_height - font_height) / 2; |
+ gfx::Rect title_bounds(title_left, title_top, 0, 0); |
+ // Size the Title text to fill the remaining space. |
+ if (!data().mini || width() >= kMiniTabRendererAsNormalTabWidth) { |
+ // If the user has big fonts, the title will appear rendered too far down |
+ // on the y-axis if we use the regular top padding, so we need to adjust it |
+ // so that the text appears centered. |
+ gfx::Size minimum_size = GetMinimumUnselectedSize(); |
+ int text_height = title_top + font_height + kBottomPadding; |
+ if (text_height > minimum_size.height()) |
+ title_top -= (text_height - minimum_size.height()) / 2; |
+ |
+ int title_width; |
+ if (showing_media_indicator_) { |
+ title_width = media_indicator_bounds_.x() - kTitleCloseButtonSpacing - |
+ title_left; |
+ } else if (close_button_->visible()) { |
+ // The close button has an empty border with some padding (see details |
+ // above where the close-button's bounds is set). Allow the title to |
+ // overlap the empty padding. |
+ title_width = close_button_->x() + close_button_->GetInsets().left() - |
+ kTitleCloseButtonSpacing - title_left; |
+ } else { |
+ title_width = lb.width() - title_left; |
+ } |
+ title_width = std::max(title_width, 0); |
+ title_bounds.SetRect(title_left, title_top, title_width, font_height); |
+ } |
+ |
+ title_bounds.set_x(GetMirroredXForRect(title_bounds)); |
+ title_->SetBoundsRect(title_bounds); |
+} |
+ |
+void Tab::OnThemeChanged() { |
+ LoadTabImages(); |
+} |
+ |
+const char* Tab::GetClassName() const { |
+ return kViewClassName; |
+} |
+ |
+bool Tab::HasHitTestMask() const { |
+ return true; |
+} |
+ |
+void Tab::GetHitTestMask(HitTestSource source, gfx::Path* path) const { |
+ // When the window is maximized we don't want to shave off the edges or top |
+ // shadow of the tab, such that the user can click anywhere along the top |
+ // edge of the screen to select a tab. Ditto for immersive fullscreen. |
+ const views::Widget* widget = GetWidget(); |
+ bool include_top_shadow = |
+ widget && (widget->IsMaximized() || widget->IsFullscreen()); |
+ TabResources::GetHitTestMask(width(), height(), include_top_shadow, path); |
+ |
+ // It is possible for a portion of the tab to be occluded if tabs are |
+ // stacked, so modify the hit test mask to only include the visible |
+ // region of the tab. |
+ gfx::Rect clip; |
+ controller_->ShouldPaintTab(this, &clip); |
+ if (clip.size().GetArea()) { |
+ SkRect intersection(path->getBounds()); |
+ intersection.intersect(RectToSkRect(clip)); |
+ path->reset(); |
+ path->addRect(intersection); |
+ } |
+} |
+ |
+bool Tab::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const { |
+ // Note: Anything that affects the tooltip text should be accounted for when |
+ // calling TooltipTextChanged() from Tab::DataChanged(). |
+ *tooltip = chrome::AssembleTabTooltipText(data_.title, data_.media_state); |
+ return !tooltip->empty(); |
+} |
+ |
+bool Tab::GetTooltipTextOrigin(const gfx::Point& p, gfx::Point* origin) const { |
+ origin->set_x(title_->x() + 10); |
+ origin->set_y(-views::TooltipManager::GetTooltipHeight() - 4); |
+ return true; |
+} |
+ |
+bool Tab::OnMousePressed(const ui::MouseEvent& event) { |
+ controller_->OnMouseEventInTab(this, event); |
+ |
+ // Allow a right click from touch to drag, which corresponds to a long click. |
+ if (event.IsOnlyLeftMouseButton() || |
+ (event.IsOnlyRightMouseButton() && event.flags() & ui::EF_FROM_TOUCH)) { |
+ ui::ListSelectionModel original_selection; |
+ original_selection.Copy(controller_->GetSelectionModel()); |
+ // Changing the selection may cause our bounds to change. If that happens |
+ // the location of the event may no longer be valid. Create a copy of the |
+ // event in the parents coordinate, which won't change, and recreate an |
+ // event after changing so the coordinates are correct. |
+ ui::MouseEvent event_in_parent(event, static_cast<View*>(this), parent()); |
+ if (controller_->SupportsMultipleSelection()) { |
+ if (event.IsShiftDown() && event.IsControlDown()) { |
+ controller_->AddSelectionFromAnchorTo(this); |
+ } else if (event.IsShiftDown()) { |
+ controller_->ExtendSelectionTo(this); |
+ } else if (event.IsControlDown()) { |
+ controller_->ToggleSelected(this); |
+ if (!IsSelected()) { |
+ // Don't allow dragging non-selected tabs. |
+ return false; |
+ } |
+ } else if (!IsSelected()) { |
+ controller_->SelectTab(this); |
+ } |
+ } else if (!IsSelected()) { |
+ controller_->SelectTab(this); |
+ } |
+ ui::MouseEvent cloned_event(event_in_parent, parent(), |
+ static_cast<View*>(this)); |
+ controller_->MaybeStartDrag(this, cloned_event, original_selection); |
+ } |
+ return true; |
+} |
+ |
+bool Tab::OnMouseDragged(const ui::MouseEvent& event) { |
+ controller_->ContinueDrag(this, event); |
+ return true; |
+} |
+ |
+void Tab::OnMouseReleased(const ui::MouseEvent& event) { |
+ controller_->OnMouseEventInTab(this, event); |
+ |
+ // Notify the drag helper that we're done with any potential drag operations. |
+ // Clean up the drag helper, which is re-created on the next mouse press. |
+ // In some cases, ending the drag will schedule the tab for destruction; if |
+ // so, bail immediately, since our members are already dead and we shouldn't |
+ // do anything else except drop the tab where it is. |
+ if (controller_->EndDrag(END_DRAG_COMPLETE)) |
+ return; |
+ |
+ // Close tab on middle click, but only if the button is released over the tab |
+ // (normal windows behavior is to discard presses of a UI element where the |
+ // releases happen off the element). |
+ if (event.IsMiddleMouseButton()) { |
+ if (HitTestPoint(event.location())) { |
+ controller_->CloseTab(this, CLOSE_TAB_FROM_MOUSE); |
+ } else if (closing_) { |
+ // We're animating closed and a middle mouse button was pushed on us but |
+ // we don't contain the mouse anymore. We assume the user is clicking |
+ // quicker than the animation and we should close the tab that falls under |
+ // the mouse. |
+ Tab* closest_tab = controller_->GetTabAt(this, event.location()); |
+ if (closest_tab) |
+ controller_->CloseTab(closest_tab, CLOSE_TAB_FROM_MOUSE); |
+ } |
+ } else if (event.IsOnlyLeftMouseButton() && !event.IsShiftDown() && |
+ !event.IsControlDown()) { |
+ // If the tab was already selected mouse pressed doesn't change the |
+ // selection. Reset it now to handle the case where multiple tabs were |
+ // selected. |
+ controller_->SelectTab(this); |
+ } |
+} |
+ |
+void Tab::OnMouseCaptureLost() { |
+ controller_->EndDrag(END_DRAG_CAPTURE_LOST); |
+} |
+ |
+void Tab::OnMouseEntered(const ui::MouseEvent& event) { |
+ hover_controller_.Show(views::GlowHoverController::SUBTLE); |
+} |
+ |
+void Tab::OnMouseMoved(const ui::MouseEvent& event) { |
+ hover_controller_.SetLocation(event.location()); |
+ controller_->OnMouseEventInTab(this, event); |
+} |
+ |
+void Tab::OnMouseExited(const ui::MouseEvent& event) { |
+ hover_controller_.Hide(); |
+} |
+ |
+void Tab::OnGestureEvent(ui::GestureEvent* event) { |
+ switch (event->type()) { |
+ case ui::ET_GESTURE_BEGIN: { |
+ if (event->details().touch_points() != 1) |
+ return; |
+ |
+ // See comment in OnMousePressed() as to why we copy the event. |
+ ui::GestureEvent event_in_parent(*event, static_cast<View*>(this), |
+ parent()); |
+ ui::ListSelectionModel original_selection; |
+ original_selection.Copy(controller_->GetSelectionModel()); |
+ tab_activated_with_last_gesture_begin_ = !IsActive(); |
+ if (!IsSelected()) |
+ controller_->SelectTab(this); |
+ gfx::Point loc(event->location()); |
+ views::View::ConvertPointToScreen(this, &loc); |
+ ui::GestureEvent cloned_event(event_in_parent, parent(), |
+ static_cast<View*>(this)); |
+ controller_->MaybeStartDrag(this, cloned_event, original_selection); |
+ break; |
+ } |
+ |
+ case ui::ET_GESTURE_END: |
+ controller_->EndDrag(END_DRAG_COMPLETE); |
+ break; |
+ |
+ case ui::ET_GESTURE_SCROLL_UPDATE: |
+ controller_->ContinueDrag(this, *event); |
+ break; |
+ |
+ default: |
+ break; |
+ } |
+ event->SetHandled(); |
+} |
+ |
+void Tab::GetAccessibleState(ui::AXViewState* state) { |
+ state->role = ui::AX_ROLE_TAB; |
+ state->name = data_.title; |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// Tab, private |
+ |
+void Tab::MaybeAdjustLeftForMiniTab(gfx::Rect* bounds) const { |
+ if (!data().mini || width() >= kMiniTabRendererAsNormalTabWidth) |
+ return; |
+ const int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth(); |
+ const int ideal_delta = width() - GetMiniWidth(); |
+ const int ideal_x = (GetMiniWidth() - bounds->width()) / 2; |
+ bounds->set_x(bounds->x() + static_cast<int>( |
+ (1 - static_cast<float>(ideal_delta) / static_cast<float>(mini_delta)) * |
+ (ideal_x - bounds->x()))); |
+} |
+ |
+void Tab::DataChanged(const TabRendererData& old) { |
+ if (data().media_state != old.media_state || data().title != old.title) |
+ TooltipTextChanged(); |
+ |
+ if (data().blocked == old.blocked) |
+ return; |
+ |
+ if (data().blocked) |
+ StartPulse(); |
+ else |
+ StopPulse(); |
+} |
+ |
+void Tab::PaintTab(gfx::Canvas* canvas) { |
+ // See if the model changes whether the icons should be painted. |
+ const bool show_icon = ShouldShowIcon(); |
+ const bool show_media_indicator = ShouldShowMediaIndicator(); |
+ const bool show_close_button = ShouldShowCloseBox(); |
+ if (show_icon != showing_icon_ || |
+ show_media_indicator != showing_media_indicator_ || |
+ show_close_button != showing_close_button_) { |
+ Layout(); |
+ } |
+ |
+ PaintTabBackground(canvas); |
+ |
+ const SkColor title_color = GetThemeProvider()->GetColor(IsSelected() ? |
+ ThemeProperties::COLOR_TAB_TEXT : |
+ ThemeProperties::COLOR_BACKGROUND_TAB_TEXT); |
+ if (!data().mini || width() > kMiniTabRendererAsNormalTabWidth) { |
+ title_->SetEnabledColor(title_color); |
+ title_->Paint(canvas, views::CullSet()); |
+ } |
+ |
+ if (show_icon) |
+ PaintIcon(canvas); |
+ |
+ if (show_media_indicator) |
+ PaintMediaIndicator(canvas); |
+ |
+ // If the close button color has changed, generate a new one. |
+ if (!close_button_color_ || title_color != close_button_color_) { |
+ close_button_color_ = title_color; |
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
+ close_button_->SetBackground(close_button_color_, |
+ rb.GetImageSkiaNamed(IDR_CLOSE_1), |
+ rb.GetImageSkiaNamed(IDR_CLOSE_1_MASK)); |
+ } |
+} |
+ |
+void Tab::PaintImmersiveTab(gfx::Canvas* canvas) { |
+ // Use transparency for the draw-attention animation. |
+ int alpha = 255; |
+ if (tab_animation_ && |
+ tab_animation_->is_animating() && |
+ !data().mini) { |
+ alpha = tab_animation_->CurrentValueBetween( |
+ 255, static_cast<int>(255 * kImmersiveTabMinThrobOpacity)); |
+ } |
+ |
+ // Draw a gray rectangle to represent the tab. This works for mini-tabs as |
+ // well as regular ones. The active tab has a brigher bar. |
+ SkColor color = |
+ IsActive() ? kImmersiveActiveTabColor : kImmersiveInactiveTabColor; |
+ gfx::Rect bar_rect = GetImmersiveBarRect(); |
+ canvas->FillRect(bar_rect, SkColorSetA(color, alpha)); |
+ |
+ // Paint network activity indicator. |
+ // TODO(jamescook): Replace this placeholder animation with a real one. |
+ // For now, let's go with a Cylon eye effect, but in blue. |
+ if (data().network_state != TabRendererData::NETWORK_STATE_NONE) { |
+ const SkColor kEyeColor = SkColorSetARGB(alpha, 71, 138, 217); |
+ int eye_width = bar_rect.width() / 3; |
+ int eye_offset = bar_rect.width() * immersive_loading_step_ / |
+ kImmersiveLoadingStepCount; |
+ if (eye_offset + eye_width < bar_rect.width()) { |
+ // Draw a single indicator strip because it fits inside |bar_rect|. |
+ gfx::Rect eye_rect( |
+ bar_rect.x() + eye_offset, 0, eye_width, kImmersiveBarHeight); |
+ canvas->FillRect(eye_rect, kEyeColor); |
+ } else { |
+ // Draw two indicators to simulate the eye "wrapping around" to the left |
+ // side. The first part fills the remainder of the bar. |
+ int right_eye_width = bar_rect.width() - eye_offset; |
+ gfx::Rect right_eye_rect( |
+ bar_rect.x() + eye_offset, 0, right_eye_width, kImmersiveBarHeight); |
+ canvas->FillRect(right_eye_rect, kEyeColor); |
+ // The second part parts the remaining |eye_width| on the left. |
+ int left_eye_width = eye_offset + eye_width - bar_rect.width(); |
+ gfx::Rect left_eye_rect( |
+ bar_rect.x(), 0, left_eye_width, kImmersiveBarHeight); |
+ canvas->FillRect(left_eye_rect, kEyeColor); |
+ } |
+ } |
+} |
+ |
+void Tab::PaintTabBackground(gfx::Canvas* canvas) { |
+ if (IsActive()) { |
+ PaintActiveTabBackground(canvas); |
+ } else { |
+ if (tab_animation_.get() && |
+ tab_animation_->is_animating() && |
+ data().mini) { |
+ gfx::MultiAnimation* animation = |
+ static_cast<gfx::MultiAnimation*>(tab_animation_.get()); |
+ PaintInactiveTabBackgroundWithTitleChange(canvas, animation); |
+ } else { |
+ PaintInactiveTabBackground(canvas); |
+ } |
+ |
+ double throb_value = GetThrobValue(); |
+ if (throb_value > 0) { |
+ canvas->SaveLayerAlpha(static_cast<int>(throb_value * 0xff), |
+ GetLocalBounds()); |
+ PaintActiveTabBackground(canvas); |
+ canvas->Restore(); |
+ } |
+ } |
+} |
+ |
+void Tab::PaintInactiveTabBackgroundWithTitleChange( |
+ gfx::Canvas* canvas, |
+ gfx::MultiAnimation* animation) { |
+ // Render the inactive tab background. We'll use this for clipping. |
+ gfx::Canvas background_canvas(size(), canvas->image_scale(), false); |
+ PaintInactiveTabBackground(&background_canvas); |
+ |
+ gfx::ImageSkia background_image(background_canvas.ExtractImageRep()); |
+ |
+ // Draw a radial gradient to hover_canvas. |
+ gfx::Canvas hover_canvas(size(), canvas->image_scale(), false); |
+ int radius = kMiniTitleChangeGradientRadius; |
+ int x0 = width() + radius - kMiniTitleChangeInitialXOffset; |
+ int x1 = radius; |
+ int x2 = -radius; |
+ int x; |
+ if (animation->current_part_index() == 0) { |
+ x = animation->CurrentValueBetween(x0, x1); |
+ } else if (animation->current_part_index() == 1) { |
+ x = x1; |
+ } else { |
+ x = animation->CurrentValueBetween(x1, x2); |
+ } |
+ SkPoint center_point; |
+ center_point.iset(x, 0); |
+ SkColor colors[2] = { kMiniTitleChangeGradientColor1, |
+ kMiniTitleChangeGradientColor2 }; |
+ skia::RefPtr<SkShader> shader = skia::AdoptRef( |
+ SkGradientShader::CreateRadial( |
+ center_point, SkIntToScalar(radius), colors, NULL, 2, |
+ SkShader::kClamp_TileMode)); |
+ SkPaint paint; |
+ paint.setShader(shader.get()); |
+ hover_canvas.DrawRect(gfx::Rect(x - radius, -radius, radius * 2, radius * 2), |
+ paint); |
+ |
+ // Draw the radial gradient clipped to the background into hover_image. |
+ gfx::ImageSkia hover_image = gfx::ImageSkiaOperations::CreateMaskedImage( |
+ gfx::ImageSkia(hover_canvas.ExtractImageRep()), background_image); |
+ |
+ // Draw the tab background to the canvas. |
+ canvas->DrawImageInt(background_image, 0, 0); |
+ |
+ // And then the gradient on top of that. |
+ if (animation->current_part_index() == 2) { |
+ uint8 alpha = animation->CurrentValueBetween(255, 0); |
+ canvas->DrawImageInt(hover_image, 0, 0, alpha); |
+ } else { |
+ canvas->DrawImageInt(hover_image, 0, 0); |
+ } |
+} |
+ |
+void Tab::PaintInactiveTabBackground(gfx::Canvas* canvas) { |
+ int tab_id; |
+ int frame_id; |
+ views::Widget* widget = GetWidget(); |
+ GetTabIdAndFrameId(widget, &tab_id, &frame_id); |
+ |
+ // Explicitly map the id so we cache correctly. |
+ const chrome::HostDesktopType host_desktop_type = GetHostDesktopType(this); |
+ tab_id = chrome::MapThemeImage(host_desktop_type, tab_id); |
+ |
+ // HasCustomImage() is only true if the theme provides the image. However, |
+ // even if the theme does not provide a tab background, the theme machinery |
+ // will make one if given a frame image. |
+ ui::ThemeProvider* theme_provider = GetThemeProvider(); |
+ const bool theme_provided_image = theme_provider->HasCustomImage(tab_id) || |
+ (frame_id != 0 && theme_provider->HasCustomImage(frame_id)); |
+ |
+ const bool can_cache = !theme_provided_image && |
+ !hover_controller_.ShouldDraw(); |
+ |
+ if (can_cache) { |
+ ui::ScaleFactor scale_factor = |
+ ui::GetSupportedScaleFactor(canvas->image_scale()); |
+ gfx::ImageSkia cached_image(GetCachedImage(tab_id, size(), scale_factor)); |
+ if (cached_image.width() == 0) { |
+ gfx::Canvas tmp_canvas(size(), canvas->image_scale(), false); |
+ PaintInactiveTabBackgroundUsingResourceId(&tmp_canvas, tab_id); |
+ cached_image = gfx::ImageSkia(tmp_canvas.ExtractImageRep()); |
+ SetCachedImage(tab_id, scale_factor, cached_image); |
+ } |
+ canvas->DrawImageInt(cached_image, 0, 0); |
+ } else { |
+ PaintInactiveTabBackgroundUsingResourceId(canvas, tab_id); |
+ } |
+} |
+ |
+void Tab::PaintInactiveTabBackgroundUsingResourceId(gfx::Canvas* canvas, |
+ int tab_id) { |
+ // WARNING: the inactive tab background may be cached. If you change what it |
+ // is drawn from you may need to update whether it can be cached. |
+ |
+ // The tab image needs to be lined up with the background image |
+ // so that it feels partially transparent. These offsets represent the tab |
+ // position within the frame background image. |
+ int offset = GetMirroredX() + background_offset_.x(); |
+ |
+ gfx::ImageSkia* tab_bg = GetThemeProvider()->GetImageSkiaNamed(tab_id); |
+ |
+ TabImage* tab_image = &tab_active_; |
+ TabImage* tab_inactive_image = &tab_inactive_; |
+ TabImage* alpha = &tab_alpha_; |
+ |
+ // If the theme is providing a custom background image, then its top edge |
+ // should be at the top of the tab. Otherwise, we assume that the background |
+ // image is a composited foreground + frame image. |
+ int bg_offset_y = GetThemeProvider()->HasCustomImage(tab_id) ? |
+ 0 : background_offset_.y(); |
+ |
+ // We need a gfx::Canvas object to be able to extract the image from. |
+ // We draw everything to this canvas and then output it to the canvas |
+ // parameter in addition to using it to mask the hover glow if needed. |
+ gfx::Canvas background_canvas(size(), canvas->image_scale(), false); |
+ |
+ // Draw left edge. Don't draw over the toolbar, as we're not the foreground |
+ // tab. |
+ gfx::ImageSkia tab_l = gfx::ImageSkiaOperations::CreateTiledImage( |
+ *tab_bg, offset, bg_offset_y, tab_image->l_width, height()); |
+ gfx::ImageSkia theme_l = |
+ gfx::ImageSkiaOperations::CreateMaskedImage(tab_l, *alpha->image_l); |
+ background_canvas.DrawImageInt(theme_l, |
+ 0, 0, theme_l.width(), theme_l.height() - kToolbarOverlap, |
+ 0, 0, theme_l.width(), theme_l.height() - kToolbarOverlap, |
+ false); |
+ |
+ // Draw right edge. Again, don't draw over the toolbar. |
+ gfx::ImageSkia tab_r = gfx::ImageSkiaOperations::CreateTiledImage(*tab_bg, |
+ offset + width() - tab_image->r_width, bg_offset_y, |
+ tab_image->r_width, height()); |
+ gfx::ImageSkia theme_r = |
+ gfx::ImageSkiaOperations::CreateMaskedImage(tab_r, *alpha->image_r); |
+ background_canvas.DrawImageInt(theme_r, |
+ 0, 0, theme_r.width(), theme_r.height() - kToolbarOverlap, |
+ width() - theme_r.width(), 0, theme_r.width(), |
+ theme_r.height() - kToolbarOverlap, false); |
+ |
+ // Draw center. Instead of masking out the top portion we simply skip over |
+ // it by incrementing by GetDropShadowHeight(), since it's a simple |
+ // rectangle. And again, don't draw over the toolbar. |
+ background_canvas.TileImageInt(*tab_bg, |
+ offset + tab_image->l_width, |
+ bg_offset_y + kDropShadowHeight, |
+ tab_image->l_width, |
+ kDropShadowHeight, |
+ width() - tab_image->l_width - tab_image->r_width, |
+ height() - kDropShadowHeight - kToolbarOverlap); |
+ |
+ canvas->DrawImageInt( |
+ gfx::ImageSkia(background_canvas.ExtractImageRep()), 0, 0); |
+ |
+ if (!GetThemeProvider()->HasCustomImage(tab_id) && |
+ hover_controller_.ShouldDraw()) { |
+ hover_controller_.Draw(canvas, gfx::ImageSkia( |
+ background_canvas.ExtractImageRep())); |
+ } |
+ |
+ // Now draw the highlights/shadows around the tab edge. |
+ canvas->DrawImageInt(*tab_inactive_image->image_l, 0, 0); |
+ canvas->TileImageInt(*tab_inactive_image->image_c, |
+ tab_inactive_image->l_width, 0, |
+ width() - tab_inactive_image->l_width - |
+ tab_inactive_image->r_width, |
+ height()); |
+ canvas->DrawImageInt(*tab_inactive_image->image_r, |
+ width() - tab_inactive_image->r_width, 0); |
+} |
+ |
+void Tab::PaintActiveTabBackground(gfx::Canvas* canvas) { |
+ gfx::ImageSkia* tab_background = |
+ GetThemeProvider()->GetImageSkiaNamed(IDR_THEME_TOOLBAR); |
+ int offset = GetMirroredX() + background_offset_.x(); |
+ |
+ TabImage* tab_image = &tab_active_; |
+ TabImage* alpha = &tab_alpha_; |
+ |
+ // Draw left edge. |
+ gfx::ImageSkia tab_l = gfx::ImageSkiaOperations::CreateTiledImage( |
+ *tab_background, offset, 0, tab_image->l_width, height()); |
+ gfx::ImageSkia theme_l = |
+ gfx::ImageSkiaOperations::CreateMaskedImage(tab_l, *alpha->image_l); |
+ canvas->DrawImageInt(theme_l, 0, 0); |
+ |
+ // Draw right edge. |
+ gfx::ImageSkia tab_r = gfx::ImageSkiaOperations::CreateTiledImage( |
+ *tab_background, |
+ offset + width() - tab_image->r_width, 0, tab_image->r_width, height()); |
+ gfx::ImageSkia theme_r = |
+ gfx::ImageSkiaOperations::CreateMaskedImage(tab_r, *alpha->image_r); |
+ canvas->DrawImageInt(theme_r, width() - tab_image->r_width, 0); |
+ |
+ // Draw center. Instead of masking out the top portion we simply skip over it |
+ // by incrementing by GetDropShadowHeight(), since it's a simple rectangle. |
+ canvas->TileImageInt(*tab_background, |
+ offset + tab_image->l_width, |
+ kDropShadowHeight, |
+ tab_image->l_width, |
+ kDropShadowHeight, |
+ width() - tab_image->l_width - tab_image->r_width, |
+ height() - kDropShadowHeight); |
+ |
+ // Now draw the highlights/shadows around the tab edge. |
+ canvas->DrawImageInt(*tab_image->image_l, 0, 0); |
+ canvas->TileImageInt(*tab_image->image_c, tab_image->l_width, 0, |
+ width() - tab_image->l_width - tab_image->r_width, height()); |
+ canvas->DrawImageInt(*tab_image->image_r, width() - tab_image->r_width, 0); |
+} |
+ |
+void Tab::PaintIcon(gfx::Canvas* canvas) { |
+ gfx::Rect bounds = favicon_bounds_; |
+ if (bounds.IsEmpty()) |
+ return; |
+ |
+ bounds.set_x(GetMirroredXForRect(bounds)); |
+ |
+ if (data().network_state != TabRendererData::NETWORK_STATE_NONE) { |
+ // Paint network activity (aka throbber) animation frame. |
+ ui::ThemeProvider* tp = GetThemeProvider(); |
+ gfx::ImageSkia frames(*tp->GetImageSkiaNamed( |
+ (data().network_state == TabRendererData::NETWORK_STATE_WAITING) ? |
+ IDR_THROBBER_WAITING : IDR_THROBBER)); |
+ |
+ int icon_size = frames.height(); |
+ int image_offset = loading_animation_frame_ * icon_size; |
+ DrawIconCenter(canvas, frames, image_offset, |
+ icon_size, icon_size, |
+ bounds, false, SkPaint()); |
+ } else if (should_display_crashed_favicon_) { |
+ // Paint crash favicon. |
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
+ gfx::ImageSkia crashed_favicon(*rb.GetImageSkiaNamed(IDR_SAD_FAVICON)); |
+ bounds.set_y(bounds.y() + favicon_hiding_offset_); |
+ DrawIconCenter(canvas, crashed_favicon, 0, |
+ crashed_favicon.width(), |
+ crashed_favicon.height(), |
+ bounds, true, SkPaint()); |
+ } else if (!data().favicon.isNull()) { |
+ // Paint the normal favicon. |
+ DrawIconCenter(canvas, data().favicon, 0, |
+ data().favicon.width(), |
+ data().favicon.height(), |
+ bounds, true, SkPaint()); |
+ } |
+} |
+ |
+void Tab::PaintMediaIndicator(gfx::Canvas* canvas) { |
+ if (media_indicator_bounds_.IsEmpty() || !media_indicator_animation_) |
+ return; |
+ |
+ gfx::Rect bounds = media_indicator_bounds_; |
+ bounds.set_x(GetMirroredXForRect(bounds)); |
+ |
+ SkPaint paint; |
+ paint.setAntiAlias(true); |
+ double opaqueness = media_indicator_animation_->GetCurrentValue(); |
+ if (data_.media_state == TAB_MEDIA_STATE_NONE) |
+ opaqueness = 1.0 - opaqueness; // Fading out, not in. |
+ paint.setAlpha(opaqueness * SK_AlphaOPAQUE); |
+ |
+ const gfx::ImageSkia& media_indicator_image = |
+ *(chrome::GetTabMediaIndicatorImage(animating_media_state_). |
+ ToImageSkia()); |
+ DrawIconAtLocation(canvas, media_indicator_image, 0, |
+ bounds.x(), bounds.y(), media_indicator_image.width(), |
+ media_indicator_image.height(), true, paint); |
+} |
+ |
+void Tab::AdvanceLoadingAnimation(TabRendererData::NetworkState old_state, |
+ TabRendererData::NetworkState state) { |
+ static bool initialized = false; |
+ static int loading_animation_frame_count = 0; |
+ static int waiting_animation_frame_count = 0; |
+ static int waiting_to_loading_frame_count_ratio = 0; |
+ if (!initialized) { |
+ initialized = true; |
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
+ gfx::ImageSkia loading_animation(*rb.GetImageSkiaNamed(IDR_THROBBER)); |
+ loading_animation_frame_count = |
+ loading_animation.width() / loading_animation.height(); |
+ gfx::ImageSkia waiting_animation(*rb.GetImageSkiaNamed( |
+ IDR_THROBBER_WAITING)); |
+ waiting_animation_frame_count = |
+ waiting_animation.width() / waiting_animation.height(); |
+ waiting_to_loading_frame_count_ratio = |
+ waiting_animation_frame_count / loading_animation_frame_count; |
+ |
+ base::debug::Alias(&loading_animation_frame_count); |
+ base::debug::Alias(&waiting_animation_frame_count); |
+ CHECK_NE(0, waiting_to_loading_frame_count_ratio) << |
+ "Number of frames in IDR_THROBBER must be equal to or greater " << |
+ "than the number of frames in IDR_THROBBER_WAITING. Please " << |
+ "investigate how this happened and update http://crbug.com/132590, " << |
+ "this is causing crashes in the wild."; |
+ } |
+ |
+ // The waiting animation is the reverse of the loading animation, but at a |
+ // different rate - the following reverses and scales the animation_frame_ |
+ // so that the frame is at an equivalent position when going from one |
+ // animation to the other. |
+ if (state != old_state) { |
+ loading_animation_frame_ = loading_animation_frame_count - |
+ (loading_animation_frame_ / waiting_to_loading_frame_count_ratio); |
+ } |
+ |
+ if (state == TabRendererData::NETWORK_STATE_WAITING) { |
+ loading_animation_frame_ = (loading_animation_frame_ + 1) % |
+ waiting_animation_frame_count; |
+ // Waiting steps backwards. |
+ immersive_loading_step_ = |
+ (immersive_loading_step_ - 1 + kImmersiveLoadingStepCount) % |
+ kImmersiveLoadingStepCount; |
+ } else if (state == TabRendererData::NETWORK_STATE_LOADING) { |
+ loading_animation_frame_ = (loading_animation_frame_ + 1) % |
+ loading_animation_frame_count; |
+ immersive_loading_step_ = (immersive_loading_step_ + 1) % |
+ kImmersiveLoadingStepCount; |
+ } else { |
+ loading_animation_frame_ = 0; |
+ immersive_loading_step_ = 0; |
+ } |
+ if (controller_->IsImmersiveStyle()) |
+ SchedulePaintInRect(GetImmersiveBarRect()); |
+ else |
+ ScheduleIconPaint(); |
+} |
+ |
+int Tab::IconCapacity() const { |
+ if (height() < GetMinimumUnselectedSize().height()) |
+ return 0; |
+ const int available_width = |
+ std::max(0, width() - kLeftPadding - kRightPadding); |
+ const int width_per_icon = gfx::kFaviconSize; |
+ const int kPaddingBetweenIcons = 2; |
+ if (available_width >= width_per_icon && |
+ available_width < (width_per_icon + kPaddingBetweenIcons)) { |
+ return 1; |
+ } |
+ return available_width / (width_per_icon + kPaddingBetweenIcons); |
+} |
+ |
+bool Tab::ShouldShowIcon() const { |
+ return chrome::ShouldTabShowFavicon( |
+ IconCapacity(), data().mini, IsActive(), data().show_icon, |
+ animating_media_state_); |
+} |
+ |
+bool Tab::ShouldShowMediaIndicator() const { |
+ return chrome::ShouldTabShowMediaIndicator( |
+ IconCapacity(), data().mini, IsActive(), data().show_icon, |
+ animating_media_state_); |
+} |
+ |
+bool Tab::ShouldShowCloseBox() const { |
+ return chrome::ShouldTabShowCloseButton( |
+ IconCapacity(), data().mini, IsActive()); |
+} |
+ |
+double Tab::GetThrobValue() { |
+ bool is_selected = IsSelected(); |
+ double min = is_selected ? kSelectedTabOpacity : 0; |
+ double scale = is_selected ? kSelectedTabThrobScale : 1; |
+ |
+ if (!data().mini) { |
+ if (tab_animation_.get() && tab_animation_->is_animating()) |
+ return tab_animation_->GetCurrentValue() * kHoverOpacity * scale + min; |
+ } |
+ |
+ if (hover_controller_.ShouldDraw()) { |
+ return kHoverOpacity * hover_controller_.GetAnimationValue() * scale + |
+ min; |
+ } |
+ |
+ return is_selected ? kSelectedTabOpacity : 0; |
+} |
+ |
+void Tab::SetFaviconHidingOffset(int offset) { |
+ favicon_hiding_offset_ = offset; |
+ ScheduleIconPaint(); |
+} |
+ |
+void Tab::DisplayCrashedFavicon() { |
+ should_display_crashed_favicon_ = true; |
+} |
+ |
+void Tab::ResetCrashedFavicon() { |
+ should_display_crashed_favicon_ = false; |
+} |
+ |
+void Tab::StopCrashAnimation() { |
+ crash_icon_animation_.reset(); |
+} |
+ |
+void Tab::StartCrashAnimation() { |
+ crash_icon_animation_.reset(new FaviconCrashAnimation(this)); |
+ crash_icon_animation_->Start(); |
+} |
+ |
+bool Tab::IsPerformingCrashAnimation() const { |
+ return crash_icon_animation_.get() && data_.IsCrashed(); |
+} |
+ |
+void Tab::StartMediaIndicatorAnimation() { |
+ media_indicator_animation_ = |
+ chrome::CreateTabMediaIndicatorFadeAnimation(data_.media_state); |
+ media_indicator_animation_->set_delegate(this); |
+ media_indicator_animation_->Start(); |
+} |
+ |
+void Tab::ScheduleIconPaint() { |
+ gfx::Rect bounds = favicon_bounds_; |
+ if (bounds.IsEmpty()) |
+ return; |
+ |
+ // Extends the area to the bottom when sad_favicon is animating. |
+ if (IsPerformingCrashAnimation()) |
+ bounds.set_height(height() - bounds.y()); |
+ bounds.set_x(GetMirroredXForRect(bounds)); |
+ SchedulePaintInRect(bounds); |
+} |
+ |
+gfx::Rect Tab::GetImmersiveBarRect() const { |
+ // The main bar is as wide as the normal tab's horizontal top line. |
+ // This top line of the tab extends a few pixels left and right of the |
+ // center image due to pixels in the rounded corner images. |
+ const int kBarPadding = 1; |
+ int main_bar_left = tab_active_.l_width - kBarPadding; |
+ int main_bar_right = width() - tab_active_.r_width + kBarPadding; |
+ return gfx::Rect( |
+ main_bar_left, 0, main_bar_right - main_bar_left, kImmersiveBarHeight); |
+} |
+ |
+void Tab::GetTabIdAndFrameId(views::Widget* widget, |
+ int* tab_id, |
+ int* frame_id) const { |
+ if (widget && |
+ widget->GetTopLevelWidget()->ShouldWindowContentsBeTransparent()) { |
+ *tab_id = IDR_THEME_TAB_BACKGROUND_V; |
+ *frame_id = 0; |
+ } else if (data().incognito) { |
+ *tab_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO; |
+ *frame_id = IDR_THEME_FRAME_INCOGNITO; |
+ } else { |
+ *tab_id = IDR_THEME_TAB_BACKGROUND; |
+ *frame_id = IDR_THEME_FRAME; |
+ } |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+// Tab, private static: |
+ |
+// static |
+void Tab::InitTabResources() { |
+ static bool initialized = false; |
+ if (initialized) |
+ return; |
+ |
+ initialized = true; |
+ image_cache_ = new ImageCache(); |
+ |
+ // Load the tab images once now, and maybe again later if the theme changes. |
+ LoadTabImages(); |
+} |
+ |
+// static |
+void Tab::LoadTabImages() { |
+ // We're not letting people override tab images just yet. |
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
+ |
+ tab_alpha_.image_l = rb.GetImageSkiaNamed(IDR_TAB_ALPHA_LEFT); |
+ tab_alpha_.image_r = rb.GetImageSkiaNamed(IDR_TAB_ALPHA_RIGHT); |
+ |
+ tab_active_.image_l = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_LEFT); |
+ tab_active_.image_c = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_CENTER); |
+ tab_active_.image_r = rb.GetImageSkiaNamed(IDR_TAB_ACTIVE_RIGHT); |
+ tab_active_.l_width = tab_active_.image_l->width(); |
+ tab_active_.r_width = tab_active_.image_r->width(); |
+ |
+ tab_inactive_.image_l = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_LEFT); |
+ tab_inactive_.image_c = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_CENTER); |
+ tab_inactive_.image_r = rb.GetImageSkiaNamed(IDR_TAB_INACTIVE_RIGHT); |
+ tab_inactive_.l_width = tab_inactive_.image_l->width(); |
+ tab_inactive_.r_width = tab_inactive_.image_r->width(); |
+} |
+ |
+// static |
+gfx::ImageSkia Tab::GetCachedImage(int resource_id, |
+ const gfx::Size& size, |
+ ui::ScaleFactor scale_factor) { |
+ for (ImageCache::const_iterator i = image_cache_->begin(); |
+ i != image_cache_->end(); ++i) { |
+ if (i->resource_id == resource_id && i->scale_factor == scale_factor && |
+ i->image.size() == size) { |
+ return i->image; |
+ } |
+ } |
+ return gfx::ImageSkia(); |
+} |
+ |
+// static |
+void Tab::SetCachedImage(int resource_id, |
+ ui::ScaleFactor scale_factor, |
+ const gfx::ImageSkia& image) { |
+ DCHECK_NE(scale_factor, ui::SCALE_FACTOR_NONE); |
+ ImageCacheEntry entry; |
+ entry.resource_id = resource_id; |
+ entry.scale_factor = scale_factor; |
+ entry.image = image; |
+ image_cache_->push_front(entry); |
+ if (image_cache_->size() > kMaxImageCacheSize) |
+ image_cache_->pop_back(); |
+} |