// Copyright (c) 2011 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/utf_string_conversions.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/themes/theme_service.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 "ui/base/animation/multi_animation.h"
#include "ui/base/animation/slide_animation.h"
#include "ui/base/animation/throb_animation.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas_skia.h"
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/font.h"
#include "ui/gfx/path.h"
#include "ui/gfx/skbitmap_operations.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;
static const double kHoverSlideOpacity = 0.5;
// 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;
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;
// 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_(0) {
InitTabResources();
}
Tab::~Tab() {
}
void Tab::StartMiniTabTitleAnimation() {
if (!mini_title_animation_.get()) {
ui::MultiAnimation::Parts parts;
parts.push_back(
ui::MultiAnimation::Part(kMiniTitleChangeAnimationDuration1MS,
ui::Tween::EASE_OUT));
parts.push_back(
ui::MultiAnimation::Part(kMiniTitleChangeAnimationDuration2MS,
ui::Tween::ZERO));
parts.push_back(
ui::MultiAnimation::Part(kMiniTitleChangeAnimationDuration3MS,
ui::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 ui::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();
}
// 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:
const gfx::Rect& Tab::GetTitleBounds() const {
return title_bounds_;
}
const gfx::Rect& Tab::GetIconBounds() const {
return favicon_bounds_;
}
void Tab::DataChanged(const TabRendererData& old) {
if (data().blocked == old.blocked)
return;
if (data().blocked)
StartPulse();
else
StopPulse();
}
////////////////////////////////////////////////////////////////////////////////
// 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;
// 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() ?
ThemeService::COLOR_TAB_TEXT :
ThemeService::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 = 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.
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_top = kTopPadding + content_height / 2 - kFaviconSize / 2;
int favicon_left = lb.x();
favicon_bounds_.SetRect(favicon_left, favicon_top,
kFaviconSize, kFaviconSize);
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() - kFaviconSize) / 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::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();
}
std::string Tab::GetClassName() const {
return kViewClassName;
}
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;
}
void Tab::OnMouseMoved(const views::MouseEvent& event) {
hover_point_ = event.location();
// We need to redraw here because otherwise the hover glow does not update
// and follow the new mouse position.
SchedulePaint();
}
////////////////////////////////////////////////////////////////////////////////
// Tab, private
void Tab::PaintTabBackground(gfx::Canvas* canvas) {
if (IsActive()) {
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) {
// 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();
int tab_id;
if (GetWidget() &&
GetWidget()->GetWindow()->non_client_view()->UseNativeFrame()) {
tab_id = IDR_THEME_TAB_BACKGROUND_V;
} else {
tab_id = data().incognito ? 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();
// We need a CanvasSkia object to be able to extract the bitmap 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::CanvasSkia background_canvas(width(), height(), false);
// 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);
background_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);
background_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.
background_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);
canvas->DrawBitmapInt(background_canvas.ExtractBitmap(), 0, 0);
if (!GetThemeProvider()->HasCustomImage(tab_id) &&
hover_animation() &&
(hover_animation()->IsShowing() || hover_animation()->is_animating())) {
SkBitmap hover_glow = DrawHoverGlowBitmap(width(), height());
// Draw the hover glow clipped to the background into hover_image.
SkBitmap hover_image = SkBitmapOperations::CreateMaskedBitmap(
hover_glow, background_canvas.ExtractBitmap());
canvas->DrawBitmapInt(hover_image, 0, 0);
}
// 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 = GetMirroredX() + background_offset_.x();
ui::ThemeProvider* tp = GetThemeProvider();
DCHECK(tp) << "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);
}
SkBitmap Tab::DrawHoverGlowBitmap(int width_input, int height_input) {
// Draw a radial gradient to hover_canvas so we can export the bitmap.
gfx::CanvasSkia hover_canvas(width_input, height_input, false);
// Draw a radial gradient to hover_canvas.
int radius = width() / 3;
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setFlags(SkPaint::kAntiAlias_Flag);
SkPoint loc = { SkIntToScalar(hover_point_.x()),
SkIntToScalar(hover_point_.y()) };
SkColor colors[2];
const ui::SlideAnimation* hover_slide = hover_animation();
int hover_alpha = 0;
if (hover_slide) {
hover_alpha = static_cast<int>(255 * kHoverSlideOpacity *
hover_slide->GetCurrentValue());
}
colors[0] = SkColorSetARGB(hover_alpha, 255, 255, 255);
colors[1] = SkColorSetARGB(0, 255, 255, 255);
SkShader* shader = SkGradientShader::CreateRadial(
loc,
SkIntToScalar(radius),
colors,
NULL,
2,
SkShader::kClamp_TileMode);
// Shader can end up null when radius = 0.
// If so, this results in default full tab glow behavior.
if (shader) {
paint.setShader(shader);
shader->unref();
hover_canvas.DrawRectInt(hover_point_.x() - radius,
hover_point_.y() - radius,
radius * 2, radius * 2, paint);
}
return hover_canvas.ExtractBitmap();
}
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 (IsActive()) {
// The active 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 active tab never clips close button.
return !data().mini && IsCloseable() && (IsActive() || IconCapacity() >= 3);
}
double Tab::GetThrobValue() {
bool is_selected = IsSelected();
double min = is_selected ? kSelectedTabOpacity : 0;
double scale = is_selected ? kSelectedTabThrobScale : 1;
if (pulse_animation() && pulse_animation()->is_animating())
return pulse_animation()->GetCurrentValue() * kHoverOpacity * scale + min;
if (hover_animation())
return kHoverOpacity * hover_animation()->GetCurrentValue() * scale + min;
return is_selected ? kSelectedTabOpacity : 0;
}
////////////////////////////////////////////////////////////////////////////////
// Tab, private:
// static
void Tab::InitTabResources() {
static bool initialized = false;
if (initialized)
return;
initialized = true;
// 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.
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();
}