#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/media_indicator_button.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 "chrome/grit/generated_resources.h"
+#include "content/public/browser/user_metrics.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/aura/env.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/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/resources/grit/ui_resources.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/view_targeter.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
+using base::UserMetricsAction;
namespace {
// Padding around the "content" of a tab, occupied by the tab border graphics.
const int kLeftPadding = 22;
-const int kTopPadding = 7;
+const int kTopPadding = 4;
const int kRightPadding = 17;
-const int kBottomPadding = 5;
+const int kBottomPadding = 2;
// Height of the shadow at the top of the tab image assets.
const int kDropShadowHeight = 4;
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 kViewSpacing = 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
widget ? widget->GetNativeView() : NULL);
}
+// Stop()s |animation| and then deletes it. We do this rather than just deleting
+// so that the delegate is notified before the destruction.
+void StopAndDeleteAnimation(scoped_ptr<gfx::Animation> animation) {
+ if (animation)
+ animation->Stop();
+}
+
} // namespace
////////////////////////////////////////////////////////////////////////////////
//
// 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 {
+class Tab::TabCloseButton : public views::ImageButton,
+ public views::MaskedTargeterDelegate {
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();
+ explicit TabCloseButton(Tab* tab)
+ : views::ImageButton(tab),
+ tab_(tab) {
+ SetEventTargeter(
+ scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
}
- // Overridden from views::View.
+ virtual ~TabCloseButton() {}
+
+ // 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.
event->SetHandled();
}
- virtual bool HasHitTestMask() const OVERRIDE {
- return true;
+ virtual const char* GetClassName() const OVERRIDE {
+ return kTabCloseButtonName;
}
- 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.
+ private:
+ // Returns the rectangular bounds of parent tab's visible region in the
+ // local coordinate space of |this|.
+ gfx::Rect GetTabBounds() const {
gfx::Path tab_mask;
- tab_->GetHitTestMask(source, &tab_mask);
+ tab_->GetHitTestMask(&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);
+ return gfx::ToEnclosingRect(tab_bounds_f);
+ }
+
+ // Returns the rectangular bounds of the tab close button in the local
+ // coordinate space of |this|, not including clipped regions on the top
+ // or bottom of the button. |tab_bounds| is the rectangular bounds of
+ // the parent tab's visible region in the local coordinate space of |this|.
+ gfx::Rect GetTabCloseButtonBounds(const gfx::Rect& tab_bounds) const {
+ gfx::Rect button_bounds(GetContentsBounds());
+ button_bounds.set_x(GetMirroredXForRect(button_bounds));
- // 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)
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();
+ return button_bounds;
+ }
+
+ // views::ViewTargeterDelegate:
+ virtual View* TargetForRect(View* root, const gfx::Rect& rect) OVERRIDE {
+ CHECK_EQ(root, this);
+
+ if (!views::UsePointBasedTargeting(rect))
+ return ViewTargeterDelegate::TargetForRect(root, rect);
+
+ // Ignore the padding set on the button.
+ gfx::Rect contents_bounds = GetContentsBounds();
+ contents_bounds.set_x(GetMirroredXForRect(contents_bounds));
+
+ // Include the padding in hit-test for touch events.
+ if (aura::Env::GetInstance()->is_touch_down())
+ contents_bounds = GetLocalBounds();
+
+ return contents_bounds.Intersects(rect) ? this : parent();
+ }
+
+ // views:MaskedTargeterDelegate:
+ virtual bool GetHitTestMask(gfx::Path* mask) const OVERRIDE {
+ DCHECK(mask);
+ mask->reset();
+
+ // The parent tab may be partially occluded by another tab if we are
+ // in stacked tab mode, which means that the tab close button may also
+ // be partially occluded. Define the hit test mask of the tab close
+ // button to be the intersection of the parent tab's visible bounds
+ // and the bounds of the tab close button.
+ gfx::Rect tab_bounds(GetTabBounds());
+ gfx::Rect button_bounds(GetTabCloseButtonBounds(tab_bounds));
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));
+ if (!intersection.IsEmpty()) {
+ mask->addRect(RectToSkRect(intersection));
+ return true;
}
+
+ return false;
}
- virtual const char* GetClassName() const OVERRIDE {
- return kTabCloseButtonName;
+ virtual bool DoesIntersectRect(const View* target,
+ const gfx::Rect& rect) const OVERRIDE {
+ CHECK_EQ(target, this);
+
+ // If the request is not made in response to a gesture, use the
+ // default implementation.
+ if (views::UsePointBasedTargeting(rect))
+ return MaskedTargeterDelegate::DoesIntersectRect(target, rect);
+
+ // The hit test request is in response to a gesture. Return false if any
+ // part of the tab close button is hidden from the user.
+ // TODO(tdanderson): Consider always returning the intersection if the
+ // non-rectangular shape of the tab can be accounted for.
+ gfx::Rect tab_bounds(GetTabBounds());
+ gfx::Rect button_bounds(GetTabCloseButtonBounds(tab_bounds));
+ if (!tab_bounds.Contains(button_bounds))
+ return false;
+
+ return MaskedTargeterDelegate::DoesIntersectRect(target, rect);
}
- private:
Tab* tab_;
DISALLOW_COPY_AND_ASSIGN(TabCloseButton);
// 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::TabImage Tab::tab_alpha_ = {0};
Tab::ImageCache* Tab::image_cache_ = NULL;
////////////////////////////////////////////////////////////////////////////////
: controller_(controller),
closing_(false),
dragging_(false),
+ detached_(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),
+ close_button_(NULL),
+ media_indicator_button_(NULL),
+ title_(new views::Label()),
+ tab_activated_with_last_tap_down_(false),
hover_controller_(this),
showing_icon_(false),
showing_media_indicator_(false),
set_id(VIEW_ID_TAB);
+ title_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
+ title_->SetElideBehavior(gfx::FADE_TAIL);
+ title_->SetAutoColorReadabilityEnabled(false);
+ title_->SetText(CoreTabHelper::GetDefaultTitle());
+ AddChildView(title_);
+
+ SetEventTargeter(
+ scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
+
// Add the Close Button.
close_button_ = new TabCloseButton(this);
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
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;
ResetCrashedFavicon();
}
- if (data_.media_state != old.media_state) {
- if (data_.media_state != TAB_MEDIA_STATE_NONE)
- animating_media_state_ = data_.media_state;
- StartMediaIndicatorAnimation();
- }
+ if (data_.media_state != old.media_state)
+ GetMediaIndicatorButton()->TransitionToMediaState(data_.media_state);
if (old.mini != data_.mini) {
- if (tab_animation_.get() && tab_animation_->is_animating()) {
- tab_animation_->Stop();
- tab_animation_.reset(NULL);
- }
+ StopAndDeleteAnimation(
+ mini_title_change_animation_.PassAs<gfx::Animation>());
}
DataChanged(old);
}
void Tab::StartPulse() {
- gfx::ThrobAnimation* animation = new gfx::ThrobAnimation(this);
- animation->SetSlideDuration(kPulseDurationMs);
+ pulse_animation_.reset(new gfx::ThrobAnimation(this));
+ pulse_animation_->SetSlideDuration(kPulseDurationMs);
if (animation_container_.get())
- animation->SetContainer(animation_container_.get());
- animation->StartThrobbing(std::numeric_limits<int>::max());
- tab_animation_.reset(animation);
+ pulse_animation_->SetContainer(animation_container_.get());
+ pulse_animation_->StartThrobbing(std::numeric_limits<int>::max());
}
void Tab::StopPulse() {
- if (!tab_animation_.get())
- return;
- tab_animation_->Stop();
- tab_animation_.reset(NULL);
+ StopAndDeleteAnimation(pulse_animation_.PassAs<gfx::Animation>());
}
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()) {
+ if (!mini_title_change_animation_) {
gfx::MultiAnimation::Parts parts;
parts.push_back(
gfx::MultiAnimation::Part(kMiniTitleChangeAnimationDuration1MS,
parts[2].end_time_ms = kMiniTitleChangeAnimationEnd3MS;
base::TimeDelta timeout =
base::TimeDelta::FromMilliseconds(kMiniTitleChangeAnimationIntervalMS);
- gfx::MultiAnimation* animation = new gfx::MultiAnimation(parts, timeout);
+ mini_title_change_animation_.reset(new gfx::MultiAnimation(parts, timeout));
if (animation_container_.get())
- animation->SetContainer(animation_container_.get());
- animation->set_delegate(this);
- tab_animation_.reset(animation);
+ mini_title_change_animation_->SetContainer(animation_container_.get());
+ mini_title_change_animation_->set_delegate(this);
}
- tab_animation_->Start();
+ mini_title_change_animation_->Start();
}
void Tab::StopMiniTabTitleAnimation() {
- if (!tab_animation_.get())
- return;
- tab_animation_->Stop();
- tab_animation_.reset(NULL);
+ StopAndDeleteAnimation(mini_title_change_animation_.PassAs<gfx::Animation>());
}
// static
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())
+ if (animation == pulse_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) {
+ if (media_indicator_button_ && media_indicator_button_->visible()) {
+ if (media_indicator_button_->enabled())
+ content::RecordAction(UserMetricsAction("CloseTab_MuteToggleAvailable"));
+ else if (data_.media_state == TAB_MEDIA_STATE_AUDIO_PLAYING)
+ content::RecordAction(UserMetricsAction("CloseTab_AudioIndicator"));
+ else
+ content::RecordAction(UserMetricsAction("CloseTab_RecordingIndicator"));
+ } else {
+ content::RecordAction(UserMetricsAction("CloseTab_NoMediaIndicator"));
+ }
+
const CloseTabSource source =
(event.type() == ui::ET_MOUSE_RELEASED &&
(event.flags() & ui::EF_FROM_TOUCH) == 0) ? CLOSE_TAB_FROM_MOUSE :
}
////////////////////////////////////////////////////////////////////////////////
+// Tab, views::MaskedTargeterDelegate overrides:
+
+bool Tab::GetHitTestMask(gfx::Path* mask) const {
+ DCHECK(mask);
+
+ // 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, mask);
+
+ // 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(mask->getBounds());
+ intersection.intersect(RectToSkRect(clip));
+ mask->reset();
+ mask->addRect(intersection);
+ }
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
// Tab, views::View overrides:
void Tab::OnPaint(gfx::Canvas* canvas) {
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.
+ lb.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding);
showing_icon_ = ShouldShowIcon();
+ favicon_bounds_.SetRect(lb.x(), lb.y(), 0, 0);
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);
+ favicon_bounds_.set_size(gfx::Size(gfx::kFaviconSize, gfx::kFaviconSize));
+ favicon_bounds_.set_y(lb.y() + (lb.height() - gfx::kFaviconSize + 1) / 2);
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
// 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_->SetBorder(views::Border::NullBorder());
+ const gfx::Size close_button_size(close_button_->GetPreferredSize());
+ const int top = lb.y() + (lb.height() - close_button_size.height() + 1) / 2;
+ const int bottom = height() - (close_button_size.height() + top);
+ const int left = kViewSpacing;
+ const int right = width() - (lb.width() + close_button_size.width() + left);
+ close_button_->SetBorder(
+ views::Border::CreateEmptyBorder(top, left, bottom, right));
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);
}
+ close_button_->SetVisible(showing_close_button_);
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);
+ views::ImageButton* const button = GetMediaIndicatorButton();
+ const gfx::Size image_size(button->GetPreferredSize());
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);
+ gfx::Rect bounds(
+ std::max(lb.x(), right - image_size.width()),
+ lb.y() + (lb.height() - image_size.height() + 1) / 2,
+ image_size.width(),
+ image_size.height());
+ MaybeAdjustLeftForMiniTab(&bounds);
+ button->SetBoundsRect(bounds);
+ button->SetVisible(true);
+ } else if (media_indicator_button_) {
+ media_indicator_button_->SetVisible(false);
}
- 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;
+ // Size the title to fill the remaining width and use all available height.
+ bool show_title = !data().mini || width() >= kMiniTabRendererAsNormalTabWidth;
+ if (show_title) {
+ int title_left = favicon_bounds_.right() + kFaviconTitleSpacing;
+ int title_width = lb.width() - title_left;
if (showing_media_indicator_) {
- title_width = media_indicator_bounds_.x() - kTitleCloseButtonSpacing -
- title_left;
+ title_width = media_indicator_button_->x() - kViewSpacing - 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.
+ // Allow the title to overlay the close button's empty border padding.
title_width = close_button_->x() + close_button_->GetInsets().left() -
- kTitleCloseButtonSpacing - title_left;
- } else {
- title_width = lb.width() - title_left;
+ kViewSpacing - 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);
+ gfx::Rect rect(title_left, lb.y(), std::max(title_width, 0), lb.height());
+ const int title_height = title_->GetPreferredSize().height();
+ if (title_height > rect.height()) {
+ rect.set_y(lb.y() - (title_height - rect.height()) / 2);
+ rect.set_height(title_height);
+ }
+ title_->SetBoundsRect(rect);
}
-
- // 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_));
+ title_->SetVisible(show_title);
}
void Tab::OnThemeChanged() {
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().
}
bool Tab::GetTooltipTextOrigin(const gfx::Point& p, gfx::Point* origin) const {
- origin->set_x(title_bounds_.x() + 10);
+ origin->set_x(title_->x() + 10);
origin->set_y(-views::TooltipManager::GetTooltipHeight() - 4);
return true;
}
// selection. Reset it now to handle the case where multiple tabs were
// selected.
controller_->SelectTab(this);
+
+ if (media_indicator_button_ && media_indicator_button_->visible() &&
+ media_indicator_button_->bounds().Contains(event.location())) {
+ content::RecordAction(UserMetricsAction("TabMediaIndicator_Clicked"));
+ }
}
}
void Tab::OnGestureEvent(ui::GestureEvent* event) {
switch (event->type()) {
- case ui::ET_GESTURE_BEGIN: {
- if (event->details().touch_points() != 1)
- return;
+ case ui::ET_GESTURE_TAP_DOWN: {
+ // TAP_DOWN is only dispatched for the first touch point.
+ DCHECK_EQ(1, event->details().touch_points());
// 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();
+ tab_activated_with_last_tap_down_ = !IsActive();
if (!IsSelected())
controller_->SelectTab(this);
gfx::Point loc(event->location());
void Tab::GetAccessibleState(ui::AXViewState* state) {
state->role = ui::AX_ROLE_TAB;
state->name = data_.title;
+ state->AddStateFlag(ui::AX_STATE_MULTISELECTABLE);
+ state->AddStateFlag(ui::AX_STATE_SELECTABLE);
+ controller_->UpdateTabAccessibilityState(this, state);
+ if (IsSelected())
+ state->AddStateFlag(ui::AX_STATE_SELECTED);
}
////////////////////////////////////////////////////////////////////////////////
// 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;
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);
+ const SkColor title_color = GetThemeProvider()->GetColor(IsSelected() ?
+ ThemeProperties::COLOR_TAB_TEXT :
+ ThemeProperties::COLOR_BACKGROUND_TAB_TEXT);
+ title_->SetVisible(!data().mini ||
+ width() > kMiniTabRendererAsNormalTabWidth);
+ title_->SetEnabledColor(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;
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(
+ if (pulse_animation_ && pulse_animation_->is_animating() && !data().mini) {
+ alpha = pulse_animation_->CurrentValueBetween(
255, static_cast<int>(255 * kImmersiveTabMinThrobOpacity));
}
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);
+ if (mini_title_change_animation_ &&
+ mini_title_change_animation_->is_animating()) {
+ PaintInactiveTabBackgroundWithTitleChange(canvas);
} else {
PaintInactiveTabBackground(canvas);
}
}
}
-void Tab::PaintInactiveTabBackgroundWithTitleChange(
- gfx::Canvas* canvas,
- gfx::MultiAnimation* animation) {
+void Tab::PaintInactiveTabBackgroundWithTitleChange(gfx::Canvas* canvas) {
// Render the inactive tab background. We'll use this for clipping.
gfx::Canvas background_canvas(size(), canvas->image_scale(), false);
PaintInactiveTabBackground(&background_canvas);
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) {
+ if (mini_title_change_animation_->current_part_index() == 0) {
+ x = mini_title_change_animation_->CurrentValueBetween(x0, x1);
+ } else if (mini_title_change_animation_->current_part_index() == 1) {
x = x1;
} else {
- x = animation->CurrentValueBetween(x1, x2);
+ x = mini_title_change_animation_->CurrentValueBetween(x1, x2);
}
SkPoint center_point;
center_point.iset(x, 0);
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);
+ if (mini_title_change_animation_->current_part_index() == 2) {
+ uint8 alpha = mini_title_change_animation_->CurrentValueBetween(255, 0);
canvas->DrawImageInt(hover_image, 0, 0, alpha);
} else {
canvas->DrawImageInt(hover_image, 0, 0);
}
void Tab::PaintIcon(gfx::Canvas* canvas) {
- gfx::Rect bounds = GetIconBounds();
+ gfx::Rect bounds = favicon_bounds_;
if (bounds.IsEmpty())
return;
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());
+ crashed_favicon.width(),
+ crashed_favicon.height(),
+ bounds, true, SkPaint());
} else if (!data().favicon.isNull()) {
// Paint the normal favicon.
DrawIconCenter(canvas, data().favicon, 0,
}
}
-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->DrawFadeTruncatingStringRect(title, gfx::Canvas::TruncateFadeTail,
- gfx::FontList(*font_), title_color, GetTitleBounds());
-}
-
void Tab::AdvanceLoadingAnimation(TabRendererData::NetworkState old_state,
TabRendererData::NetworkState state) {
static bool initialized = false;
bool Tab::ShouldShowIcon() const {
return chrome::ShouldTabShowFavicon(
IconCapacity(), data().mini, IsActive(), data().show_icon,
- animating_media_state_);
+ media_indicator_button_ ? media_indicator_button_->showing_media_state() :
+ data_.media_state);
}
bool Tab::ShouldShowMediaIndicator() const {
return chrome::ShouldTabShowMediaIndicator(
IconCapacity(), data().mini, IsActive(), data().show_icon,
- animating_media_state_);
+ media_indicator_button_ ? media_indicator_button_->showing_media_state() :
+ data_.media_state);
}
bool Tab::ShouldShowCloseBox() const {
}
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;
+ const bool is_selected = IsSelected();
+ const double min = is_selected ? kSelectedTabOpacity : 0;
+ const double scale = is_selected ? kSelectedTabThrobScale : 1;
+
+ // Showing both the pulse and title change animation at the same time is too
+ // much.
+ if (pulse_animation_ && pulse_animation_->is_animating() &&
+ (!mini_title_change_animation_ ||
+ !mini_title_change_animation_->is_animating())) {
+ return pulse_animation_->GetCurrentValue() * kHoverOpacity * scale + min;
}
if (hover_controller_.ShouldDraw()) {
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();
+ gfx::Rect bounds = favicon_bounds_;
if (bounds.IsEmpty())
return;
- // Extends the area to the bottom when sad_favicon is
- // animating.
+ // Extends the area to the bottom when sad_favicon is animating.
if (IsPerformingCrashAnimation())
bounds.set_height(height() - bounds.y());
bounds.set_x(GetMirroredXForRect(bounds));
}
}
+MediaIndicatorButton* Tab::GetMediaIndicatorButton() {
+ if (!media_indicator_button_) {
+ media_indicator_button_ = new MediaIndicatorButton();
+ AddChildView(media_indicator_button_); // Takes ownership.
+ }
+ return media_indicator_button_;
+}
+
////////////////////////////////////////////////////////////////////////////////
// Tab, private static:
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.