diff options
Diffstat (limited to 'chrome/browser/ui/views/tabs/tab.cc')
-rw-r--r-- | chrome/browser/ui/views/tabs/tab.cc | 610 |
1 files changed, 610 insertions, 0 deletions
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc new file mode 100644 index 0000000..dda22fb --- /dev/null +++ b/chrome/browser/ui/views/tabs/tab.cc @@ -0,0 +1,610 @@ +// Copyright (c) 2010 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/views/tabs/tab.h" + +#include <limits> + +#include "app/multi_animation.h" +#include "app/resource_bundle.h" +#include "app/slide_animation.h" +#include "app/throb_animation.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/defaults.h" +#include "chrome/browser/themes/browser_theme_provider.h" +#include "gfx/canvas_skia.h" +#include "gfx/favicon_size.h" +#include "gfx/font.h" +#include "gfx/path.h" +#include "gfx/skbitmap_operations.h" +#include "grit/app_resources.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "third_party/skia/include/effects/SkGradientShader.h" +#include "views/controls/button/image_button.h" +#include "views/widget/tooltip_manager.h" +#include "views/widget/widget.h" +#include "views/window/non_client_view.h" +#include "views/window/window.h" + +static const int kLeftPadding = 16; +static const int kTopPadding = 6; +static const int kRightPadding = 15; +static const int kBottomPadding = 5; +static const int kDropShadowHeight = 2; +static const int kToolbarOverlap = 1; +static const int kFavIconTitleSpacing = 4; +static const int kTitleCloseButtonSpacing = 5; +static const int kStandardTitleWidth = 175; +static const int kCloseButtonVertFuzz = 0; +static const int kCloseButtonHorzFuzz = 5; + +// Vertical adjustment to the favicon when the tab has a large icon. +static const int kAppTapFaviconVerticalAdjustment = 2; + +// 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; + +Tab::TabImage Tab::tab_alpha = {0}; +Tab::TabImage Tab::tab_active = {0}; +Tab::TabImage Tab::tab_inactive = {0}; + +// 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; + +// 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); + +// Hit mask constants. +static const SkScalar kTabCapWidth = 15; +static const SkScalar kTabTopCurveWidth = 4; +static const SkScalar kTabBottomCurveWidth = 3; + +namespace { + +void InitTabResources() { + static bool initialized = false; + if (initialized) + return; + + initialized = true; + Tab::LoadTabImages(); +} + +} // namespace + +// static +const char Tab::kViewClassName[] = "browser/tabs/Tab"; + +//////////////////////////////////////////////////////////////////////////////// +// Tab, public: + +Tab::Tab(TabController* controller) + : BaseTab(controller), + showing_icon_(false), + showing_close_button_(false), + close_button_color_(NULL) { + InitTabResources(); +} + +Tab::~Tab() { +} + +void Tab::StartMiniTabTitleAnimation() { + if (!mini_title_animation_.get()) { + MultiAnimation::Parts parts; + parts.push_back(MultiAnimation::Part(kMiniTitleChangeAnimationDuration1MS, + Tween::EASE_OUT)); + parts.push_back(MultiAnimation::Part(kMiniTitleChangeAnimationDuration2MS, + Tween::ZERO)); + parts.push_back(MultiAnimation::Part(kMiniTitleChangeAnimationDuration3MS, + 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; + mini_title_animation_.reset(new MultiAnimation(parts)); + mini_title_animation_->SetContainer(animation_container()); + mini_title_animation_->set_delegate(this); + } + mini_title_animation_->Start(); +} + +void Tab::StopMiniTabTitleAnimation() { + if (mini_title_animation_.get()) + mini_title_animation_->Stop(); +} + +void Tab::PaintIcon(gfx::Canvas* canvas) { + BaseTab::PaintIcon(canvas, favicon_bounds_.x(), favicon_bounds_.y()); +} + +// static +gfx::Size Tab::GetMinimumUnselectedSize() { + InitTabResources(); + + gfx::Size minimum_size; + minimum_size.set_width(kLeftPadding + kRightPadding); + // Since we use bitmap 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; +} + +// static +gfx::Size Tab::GetMinimumSelectedSize() { + gfx::Size minimum_size = GetMinimumUnselectedSize(); + minimum_size.set_width(kLeftPadding + kFavIconSize + kRightPadding); + return minimum_size; +} + +// static +gfx::Size Tab::GetStandardSize() { + gfx::Size standard_size = GetMinimumUnselectedSize(); + standard_size.set_width( + standard_size.width() + kFavIconTitleSpacing + kStandardTitleWidth); + return standard_size; +} + +// static +int Tab::GetMiniWidth() { + return browser_defaults::kMiniTabWidth; +} + +//////////////////////////////////////////////////////////////////////////////// +// Tab, protected: + +void Tab::DataChanged(const TabRendererData& old) { + if (data().blocked == old.blocked) + return; + + if (data().blocked) + StartPulse(); + else + StopPulse(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Tab, views::View overrides: + +void Tab::Paint(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; + + // See if the model changes whether the icons should be painted. + const bool show_icon = ShouldShowIcon(); + const bool show_close_button = ShouldShowCloseBox(); + if (show_icon != showing_icon_ || show_close_button != showing_close_button_) + Layout(); + + PaintTabBackground(canvas); + + SkColor title_color = GetThemeProvider()-> + GetColor(IsSelected() ? + BrowserThemeProvider::COLOR_TAB_TEXT : + BrowserThemeProvider::COLOR_BACKGROUND_TAB_TEXT); + + if (!data().mini || width() > kMiniTabRendererAsNormalTabWidth) + PaintTitle(canvas, title_color); + + if (show_icon) + PaintIcon(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; + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + close_button()->SetBackground(close_button_color_, + rb.GetBitmapNamed(IDR_TAB_CLOSE), + rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK)); + } +} + +void Tab::Layout() { + gfx::Rect lb = GetLocalBounds(false); + 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. + int content_height = std::max(kFavIconSize, font_height()); + 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_size = + !data().favicon.empty() ? data().favicon.width() : kFavIconSize; + int favicon_top = kTopPadding + content_height / 2 - favicon_size / 2; + int favicon_left = lb.x(); + if (favicon_size != kFavIconSize) { + favicon_left -= (favicon_size - kFavIconSize) / 2; + favicon_top -= kAppTapFaviconVerticalAdjustment; + } + favicon_bounds_.SetRect(favicon_left, favicon_top, + favicon_size, favicon_size); + if (data().mini && width() < kMiniTabRendererAsNormalTabWidth) { + // Adjust the location of the favicon when transitioning from a normal + // tab to a mini-tab. + int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth(); + int ideal_delta = width() - GetMiniWidth(); + if (ideal_delta < mini_delta) { + int ideal_x = (GetMiniWidth() - favicon_size) / 2; + int x = favicon_bounds_.x() + static_cast<int>( + (1 - static_cast<float>(ideal_delta) / + static_cast<float>(mini_delta)) * + (ideal_x - favicon_bounds_.x())); + favicon_bounds_.set_x(x); + } + } + } else { + favicon_bounds_.SetRect(lb.x(), lb.y(), 0, 0); + } + + // Size the Close button. + showing_close_button_ = ShouldShowCloseBox(); + if (showing_close_button_) { + int close_button_top = kTopPadding + kCloseButtonVertFuzz + + (content_height - close_button_size.height()) / 2; + // If the ratio of the close button size to tab width exceeds the maximum. + close_button()->SetBounds(lb.width() + kCloseButtonHorzFuzz, + close_button_top, close_button_size.width(), + close_button_size.height()); + close_button()->SetVisible(true); + } else { + close_button()->SetBounds(0, 0, 0, 0); + close_button()->SetVisible(false); + } + + int title_left = favicon_bounds_.right() + kFavIconTitleSpacing; + int title_top = kTopPadding + (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 (close_button()->IsVisible()) { + title_width = std::max(close_button()->x() - + kTitleCloseButtonSpacing - title_left, 0); + } else { + title_width = std::max(lb.width() - title_left, 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::Paint(). 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(MirroredLeftPointForRect(title_bounds_)); +} + +void Tab::OnThemeChanged() { + Tab::LoadTabImages(); +} + +bool Tab::HasHitTestMask() const { + return true; +} + +void Tab::GetHitTestMask(gfx::Path* path) const { + DCHECK(path); + + SkScalar h = SkIntToScalar(height()); + SkScalar w = SkIntToScalar(width()); + + path->moveTo(0, h); + + // Left end cap. + path->lineTo(kTabBottomCurveWidth, h - kTabBottomCurveWidth); + path->lineTo(kTabCapWidth - kTabTopCurveWidth, kTabTopCurveWidth); + path->lineTo(kTabCapWidth, 0); + + // Connect to the right cap. + path->lineTo(w - kTabCapWidth, 0); + + // Right end cap. + path->lineTo(w - kTabCapWidth + kTabTopCurveWidth, kTabTopCurveWidth); + path->lineTo(w - kTabBottomCurveWidth, h - kTabBottomCurveWidth); + path->lineTo(w, h); + + // Close out the path. + path->lineTo(0, h); + path->close(); +} + +bool Tab::GetTooltipTextOrigin(const gfx::Point& p, gfx::Point* origin) { + origin->set_x(title_bounds().x() + 10); + origin->set_y(-views::TooltipManager::GetTooltipHeight() - 4); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// Tab, private + +void Tab::PaintTabBackground(gfx::Canvas* canvas) { + if (IsSelected()) { + PaintActiveTabBackground(canvas); + } else { + if (mini_title_animation_.get() && mini_title_animation_->is_animating()) + PaintInactiveTabBackgroundWithTitleChange(canvas); + else + PaintInactiveTabBackground(canvas); + + double throb_value = GetThrobValue(); + if (throb_value > 0) { + canvas->SaveLayerAlpha(static_cast<int>(throb_value * 0xff), + gfx::Rect(width(), height())); + canvas->AsCanvasSkia()->drawARGB(0, 255, 255, 255, + SkXfermode::kClear_Mode); + PaintActiveTabBackground(canvas); + canvas->Restore(); + } + } +} + +void Tab::PaintInactiveTabBackgroundWithTitleChange(gfx::Canvas* canvas) { + // Render the inactive tab background. We'll use this for clipping. + gfx::CanvasSkia background_canvas(width(), height(), false); + PaintInactiveTabBackground(&background_canvas); + + SkBitmap background_image = background_canvas.ExtractBitmap(); + + // Draw a radial gradient to hover_canvas. + gfx::CanvasSkia hover_canvas(width(), height(), false); + int radius = kMiniTitleChangeGradientRadius; + int x0 = width() + radius - kMiniTitleChangeInitialXOffset; + int x1 = radius; + int x2 = -radius; + int x; + if (mini_title_animation_->current_part_index() == 0) { + x = mini_title_animation_->CurrentValueBetween(x0, x1); + } else if (mini_title_animation_->current_part_index() == 1) { + x = x1; + } else { + x = mini_title_animation_->CurrentValueBetween(x1, x2); + } + SkPaint paint; + SkPoint loc = { SkIntToScalar(x), SkIntToScalar(0) }; + SkColor colors[2]; + colors[0] = kMiniTitleChangeGradientColor1; + colors[1] = kMiniTitleChangeGradientColor2; + SkShader* shader = SkGradientShader::CreateRadial( + loc, + SkIntToScalar(radius), + colors, + NULL, + 2, + SkShader::kClamp_TileMode); + paint.setShader(shader); + shader->unref(); + hover_canvas.DrawRectInt(x - radius, -radius, radius * 2, radius * 2, paint); + + // Draw the radial gradient clipped to the background into hover_image. + SkBitmap hover_image = SkBitmapOperations::CreateMaskedBitmap( + hover_canvas.ExtractBitmap(), background_image); + + // Draw the tab background to the canvas. + canvas->DrawBitmapInt(background_image, 0, 0); + + // And then the gradient on top of that. + if (mini_title_animation_->current_part_index() == 2) { + canvas->SaveLayerAlpha(mini_title_animation_->CurrentValueBetween(255, 0)); + canvas->DrawBitmapInt(hover_image, 0, 0); + canvas->Restore(); + } else { + canvas->DrawBitmapInt(hover_image, 0, 0); + } +} + +void Tab::PaintInactiveTabBackground(gfx::Canvas* canvas) { + bool is_otr = data().off_the_record; + + // 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 = GetX(views::View::APPLY_MIRRORING_TRANSFORMATION) + + background_offset_.x(); + + int tab_id; + if (GetWidget() && + GetWidget()->GetWindow()->GetNonClientView()->UseNativeFrame()) { + tab_id = IDR_THEME_TAB_BACKGROUND_V; + } else { + tab_id = is_otr ? IDR_THEME_TAB_BACKGROUND_INCOGNITO : + IDR_THEME_TAB_BACKGROUND; + } + + SkBitmap* tab_bg = GetThemeProvider()->GetBitmapNamed(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(); + + // Draw left edge. Don't draw over the toolbar, as we're not the foreground + // tab. + SkBitmap tab_l = SkBitmapOperations::CreateTiledBitmap( + *tab_bg, offset, bg_offset_y, tab_image->l_width, height()); + SkBitmap theme_l = + SkBitmapOperations::CreateMaskedBitmap(tab_l, *alpha->image_l); + canvas->DrawBitmapInt(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. + SkBitmap tab_r = SkBitmapOperations::CreateTiledBitmap(*tab_bg, + offset + width() - tab_image->r_width, bg_offset_y, + tab_image->r_width, height()); + SkBitmap theme_r = + SkBitmapOperations::CreateMaskedBitmap(tab_r, *alpha->image_r); + canvas->DrawBitmapInt(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 kDropShadowHeight, since it's a simple rectangle. + // And again, don't draw over the toolbar. + canvas->TileImageInt(*tab_bg, + offset + tab_image->l_width, + bg_offset_y + kDropShadowHeight + tab_image->y_offset, + tab_image->l_width, + kDropShadowHeight + tab_image->y_offset, + width() - tab_image->l_width - tab_image->r_width, + height() - kDropShadowHeight - kToolbarOverlap - tab_image->y_offset); + + // Now draw the highlights/shadows around the tab edge. + canvas->DrawBitmapInt(*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->DrawBitmapInt(*tab_inactive_image->image_r, + width() - tab_inactive_image->r_width, 0); +} + +void Tab::PaintActiveTabBackground(gfx::Canvas* canvas) { + int offset = GetX(views::View::APPLY_MIRRORING_TRANSFORMATION) + + background_offset_.x(); + ThemeProvider* tp = GetThemeProvider(); + if (!tp) + NOTREACHED() << "Unable to get theme provider"; + + SkBitmap* tab_bg = GetThemeProvider()->GetBitmapNamed(IDR_THEME_TOOLBAR); + + TabImage* tab_image = &tab_active; + TabImage* alpha = &tab_alpha; + + // Draw left edge. + SkBitmap tab_l = SkBitmapOperations::CreateTiledBitmap( + *tab_bg, offset, 0, tab_image->l_width, height()); + SkBitmap theme_l = + SkBitmapOperations::CreateMaskedBitmap(tab_l, *alpha->image_l); + canvas->DrawBitmapInt(theme_l, 0, 0); + + // Draw right edge. + SkBitmap tab_r = SkBitmapOperations::CreateTiledBitmap(*tab_bg, + offset + width() - tab_image->r_width, 0, tab_image->r_width, height()); + SkBitmap theme_r = + SkBitmapOperations::CreateMaskedBitmap(tab_r, *alpha->image_r); + canvas->DrawBitmapInt(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 kDropShadowHeight, since it's a simple rectangle. + canvas->TileImageInt(*tab_bg, + offset + tab_image->l_width, + kDropShadowHeight + tab_image->y_offset, + tab_image->l_width, + kDropShadowHeight + tab_image->y_offset, + width() - tab_image->l_width - tab_image->r_width, + height() - kDropShadowHeight - tab_image->y_offset); + + // Now draw the highlights/shadows around the tab edge. + canvas->DrawBitmapInt(*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->DrawBitmapInt(*tab_image->image_r, width() - tab_image->r_width, 0); +} + +int Tab::IconCapacity() const { + if (height() < GetMinimumUnselectedSize().height()) + return 0; + return (width() - kLeftPadding - kRightPadding) / kFavIconSize; +} + +bool Tab::ShouldShowIcon() const { + if (data().mini && height() >= GetMinimumUnselectedSize().height()) + return true; + if (!data().show_icon) { + return false; + } else if (IsSelected()) { + // The selected tab clips favicon before close button. + return IconCapacity() >= 2; + } + // Non-selected tabs clip close button before favicon. + return IconCapacity() >= 1; +} + +bool Tab::ShouldShowCloseBox() const { + // The selected tab never clips close button. + return !data().mini && IsCloseable() && + (IsSelected() || IconCapacity() >= 3); +} + +double Tab::GetThrobValue() { + if (pulse_animation() && pulse_animation()->is_animating()) + return pulse_animation()->GetCurrentValue() * kHoverOpacity; + + return hover_animation() ? + kHoverOpacity * hover_animation()->GetCurrentValue() : 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// Tab, private: + +// static +void Tab::LoadTabImages() { + // We're not letting people override tab images just yet. + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + + tab_alpha.image_l = rb.GetBitmapNamed(IDR_TAB_ALPHA_LEFT); + tab_alpha.image_r = rb.GetBitmapNamed(IDR_TAB_ALPHA_RIGHT); + + tab_active.image_l = rb.GetBitmapNamed(IDR_TAB_ACTIVE_LEFT); + tab_active.image_c = rb.GetBitmapNamed(IDR_TAB_ACTIVE_CENTER); + tab_active.image_r = rb.GetBitmapNamed(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.GetBitmapNamed(IDR_TAB_INACTIVE_LEFT); + tab_inactive.image_c = rb.GetBitmapNamed(IDR_TAB_INACTIVE_CENTER); + tab_inactive.image_r = rb.GetBitmapNamed(IDR_TAB_INACTIVE_RIGHT); + tab_inactive.l_width = tab_inactive.image_l->width(); + tab_inactive.r_width = tab_inactive.image_r->width(); +} |