Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package

tab.cc

// 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/browser_theme_provider.h"
#include "chrome/browser/defaults.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().phantom)
    StopMiniTabTitleAnimation();

  if (data().blocked != old.blocked) {
    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() && !data().phantom;
  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.FillRectInt(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();
}

Generated by  Doxygen 1.6.0   Back to index