summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui
diff options
context:
space:
mode:
authorsky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-08 22:40:15 +0000
committersky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-08 22:40:15 +0000
commit5ffa75d42d901f303b10702717272f1ea83607e7 (patch)
tree0fa6adc719c98353ab51108a9e03ebf4ddfc98a6 /chrome/browser/ui
parentdda0b7b7ca05ab37d35b25717d574e5c7db7cc29 (diff)
downloadchromium_src-5ffa75d42d901f303b10702717272f1ea83607e7.zip
chromium_src-5ffa75d42d901f303b10702717272f1ea83607e7.tar.gz
chromium_src-5ffa75d42d901f303b10702717272f1ea83607e7.tar.bz2
Adds scroll buttons to side tabs so that you can get to all the tabs.
BUG=55287 TEST=none R=ben@chromium.org Review URL: http://codereview.chromium.org/6822006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@81004 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/ui')
-rw-r--r--chrome/browser/ui/views/tabs/base_tab_strip.cc14
-rw-r--r--chrome/browser/ui/views/tabs/base_tab_strip.h10
-rw-r--r--chrome/browser/ui/views/tabs/side_tab_strip.cc206
-rw-r--r--chrome/browser/ui/views/tabs/side_tab_strip.h36
4 files changed, 247 insertions, 19 deletions
diff --git a/chrome/browser/ui/views/tabs/base_tab_strip.cc b/chrome/browser/ui/views/tabs/base_tab_strip.cc
index 72a0f67..2a980df 100644
--- a/chrome/browser/ui/views/tabs/base_tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/base_tab_strip.cc
@@ -126,10 +126,6 @@ BaseTabStrip::BaseTabStrip(TabStripController* controller, Type type)
BaseTabStrip::~BaseTabStrip() {
}
-bool BaseTabStrip::IsAnimating() const {
- return bounds_animator_.IsAnimating();
-}
-
void BaseTabStrip::AddTabAt(int model_index, const TabRendererData& data) {
BaseTab* tab = CreateTab();
tab->SetData(data);
@@ -150,14 +146,11 @@ void BaseTabStrip::AddTabAt(int model_index, const TabRendererData& data) {
void BaseTabStrip::MoveTab(int from_model_index, int to_model_index) {
int from_tab_data_index = ModelIndexToTabIndex(from_model_index);
-
BaseTab* tab = tab_data_[from_tab_data_index].tab;
tab_data_.erase(tab_data_.begin() + from_tab_data_index);
TabData data = {tab, gfx::Rect()};
-
int to_tab_data_index = ModelIndexToTabIndex(to_model_index);
-
tab_data_.insert(tab_data_.begin() + to_tab_data_index, data);
StartMoveTabAnimation();
@@ -563,8 +556,11 @@ void BaseTabStrip::DoLayout() {
SchedulePaint();
}
-BaseTab* BaseTabStrip::GetTabAtLocal(
- const gfx::Point& local_point) {
+bool BaseTabStrip::IsAnimating() const {
+ return bounds_animator_.IsAnimating();
+}
+
+BaseTab* BaseTabStrip::GetTabAtLocal(const gfx::Point& local_point) {
views::View* view = GetEventHandlerForPoint(local_point);
if (!view)
return NULL; // No tab contains the point.
diff --git a/chrome/browser/ui/views/tabs/base_tab_strip.h b/chrome/browser/ui/views/tabs/base_tab_strip.h
index 210080c..14254cb 100644
--- a/chrome/browser/ui/views/tabs/base_tab_strip.h
+++ b/chrome/browser/ui/views/tabs/base_tab_strip.h
@@ -33,10 +33,6 @@ class BaseTabStrip : public AbstractTabStripView,
Type type() const { return type_; }
- // Returns true if Tabs in this TabStrip are currently changing size or
- // position.
- virtual bool IsAnimating() const;
-
// Starts highlighting the tab at the specified index.
virtual void StartHighlight(int model_index) = 0;
@@ -87,7 +83,7 @@ class BaseTabStrip : public AbstractTabStripView,
// Returns the index of the specified tab in the model coordiate system, or
// -1 if tab is closing or not valid.
- virtual int GetModelIndexOfBaseTab(const BaseTab* tab) const;
+ int GetModelIndexOfBaseTab(const BaseTab* tab) const;
// Gets the number of Tabs in the tab strip.
// WARNING: this is the number of tabs displayed by the tabstrip, which if
@@ -246,6 +242,10 @@ class BaseTabStrip : public AbstractTabStripView,
// Invoked from Layout if the size changes or layout is really needed.
virtual void DoLayout();
+ // Returns true if Tabs in this TabStrip are currently changing size or
+ // position.
+ bool IsAnimating() const;
+
// Get tab at a point in local view coordinates.
BaseTab* GetTabAtLocal(const gfx::Point& local_point);
diff --git a/chrome/browser/ui/views/tabs/side_tab_strip.cc b/chrome/browser/ui/views/tabs/side_tab_strip.cc
index 4e1ae21..1c6fa87 100644
--- a/chrome/browser/ui/views/tabs/side_tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/side_tab_strip.cc
@@ -14,16 +14,24 @@
#include "ui/gfx/canvas.h"
#include "views/background.h"
#include "views/controls/button/image_button.h"
+#include "views/controls/button/text_button.h"
namespace {
+
const int kVerticalTabSpacing = 2;
const int kTabStripWidth = 140;
const SkColor kBackgroundColor = SkColorSetARGB(255, 209, 220, 248);
const SkColor kSeparatorColor = SkColorSetARGB(255, 151, 159, 179);
+// Height of the scroll buttons.
+const int kScrollButtonHeight = 20;
+
// Height of the separator.
const int kSeparatorHeight = 1;
+// Padding between tabs and scroll button.
+const int kScrollButtonVerticalPadding = 2;
+
// The new tab button is rendered using a SideTab.
class SideTabNewTabButton : public SideTab {
public:
@@ -61,6 +69,49 @@ void SideTabNewTabButton::OnMouseReleased(const views::MouseEvent& event) {
controller_->CreateNewTab();
}
+// Button class used for the scroll buttons.
+class ScrollButton : public views::TextButton {
+ public:
+ enum Type {
+ UP,
+ DOWN
+ };
+
+ explicit ScrollButton(views::ButtonListener* listener, Type type);
+
+ protected:
+ // views::View overrides.
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+
+ private:
+ const Type type_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScrollButton);
+};
+
+ScrollButton::ScrollButton(views::ButtonListener* listener,
+ Type type)
+ : views::TextButton(listener, std::wstring()),
+ type_(type) {
+}
+
+void ScrollButton::OnPaint(gfx::Canvas* canvas) {
+ TextButton::OnPaint(canvas);
+
+ // Draw the arrow.
+ SkColor arrow_color = IsEnabled() ? SK_ColorBLACK : SK_ColorGRAY;
+ int arrow_height = 5;
+ int x = width() / 2;
+ int y = (height() - arrow_height) / 2;
+ int delta_y = 1;
+ if (type_ == DOWN) {
+ delta_y = -1;
+ y += arrow_height;
+ }
+ for (int i = 0; i < arrow_height; ++i, --x, y += delta_y)
+ canvas->FillRectInt(arrow_color, x, y, (i * 2) + 1, 1);
+}
+
} // namespace
// static
@@ -72,13 +123,21 @@ const int SideTabStrip::kTabStripInset = 3;
SideTabStrip::SideTabStrip(TabStripController* controller)
: BaseTabStrip(controller, BaseTabStrip::VERTICAL_TAB_STRIP),
newtab_button_(new SideTabNewTabButton(controller)),
- separator_(new views::View()) {
+ scroll_up_button_(NULL),
+ scroll_down_button_(NULL),
+ separator_(new views::View()),
+ first_tab_y_offset_(0),
+ ideal_height_(0) {
SetID(VIEW_ID_TAB_STRIP);
set_background(views::Background::CreateSolidBackground(kBackgroundColor));
AddChildView(newtab_button_);
separator_->set_background(
views::Background::CreateSolidBackground(kSeparatorColor));
AddChildView(separator_);
+ scroll_up_button_ = new ScrollButton(this, ScrollButton::UP);
+ AddChildView(scroll_up_button_);
+ scroll_down_button_ = new ScrollButton(this, ScrollButton::DOWN);
+ AddChildView(scroll_down_button_);
}
SideTabStrip::~SideTabStrip() {
@@ -118,6 +177,9 @@ void SideTabStrip::RemoveTabAt(int model_index) {
void SideTabStrip::SelectTabAt(int old_model_index, int new_model_index) {
GetBaseTabAtModelIndex(new_model_index)->SchedulePaint();
+
+ if (controller()->IsActiveTab(new_model_index))
+ MakeTabVisible(ModelIndexToTabIndex(new_model_index));
}
void SideTabStrip::TabTitleChangedNotLoading(int model_index) {
@@ -132,6 +194,12 @@ void SideTabStrip::PaintChildren(gfx::Canvas* canvas) {
// them last.
std::vector<BaseTab*> dragging_tabs;
+ // Make sure nothing draws on top of the scroll buttons.
+ canvas->Save();
+ canvas->ClipRectInt(kTabStripInset, kTabStripInset,
+ width() - kTabStripInset - kTabStripInset,
+ GetMaxTabY() - kTabStripInset);
+
// Paint the new tab and separator first so that any tabs animating appear on
// top.
separator_->Paint(canvas);
@@ -147,6 +215,74 @@ void SideTabStrip::PaintChildren(gfx::Canvas* canvas) {
for (size_t i = 0; i < dragging_tabs.size(); ++i)
dragging_tabs[i]->Paint(canvas);
+
+ canvas->Restore();
+
+ scroll_down_button_->Paint(canvas);
+ scroll_up_button_->Paint(canvas);
+}
+
+views::View* SideTabStrip::GetEventHandlerForPoint(const gfx::Point& point) {
+ // Check the scroll buttons first as they visually appear on top of everything
+ // else.
+ if (scroll_down_button_->IsVisible()) {
+ gfx::Point local_point(point);
+ View::ConvertPointToView(this, scroll_down_button_, &local_point);
+ if (scroll_down_button_->HitTest(local_point))
+ return scroll_down_button_->GetEventHandlerForPoint(local_point);
+ }
+
+ if (scroll_up_button_->IsVisible()) {
+ gfx::Point local_point(point);
+ View::ConvertPointToView(this, scroll_up_button_, &local_point);
+ if (scroll_up_button_->HitTest(local_point))
+ return scroll_up_button_->GetEventHandlerForPoint(local_point);
+ }
+ return views::View::GetEventHandlerForPoint(point);
+}
+
+void SideTabStrip::ButtonPressed(views::Button* sender,
+ const views::Event& event) {
+ int max_offset = GetMaxOffset();
+ if (max_offset == 0) {
+ // All the tabs fit, no need to scroll.
+ return;
+ }
+
+ // Determine the index of the first visible tab.
+ int initial_y = kTabStripInset;
+ int first_vis_index = -1;
+ for (int i = 0; i < tab_count(); ++i) {
+ if (ideal_bounds(i).bottom() > initial_y) {
+ first_vis_index = i;
+ break;
+ }
+ }
+ if (first_vis_index == -1)
+ return;
+
+ int delta = 0;
+ if (sender == scroll_up_button_) {
+ delta = initial_y - ideal_bounds(first_vis_index).y();
+ if (delta <= 0) {
+ if (first_vis_index == 0) {
+ delta = -first_tab_y_offset_;
+ } else {
+ delta = initial_y - ideal_bounds(first_vis_index - 1).y();
+ DCHECK_NE(0, delta); // Not fatal, but indicates we aren't scrolling.
+ }
+ }
+ } else {
+ DCHECK_EQ(sender, scroll_down_button_);
+ if (ideal_bounds(first_vis_index).y() > initial_y) {
+ delta = initial_y - ideal_bounds(first_vis_index).y();
+ } else if (first_vis_index + 1 == tab_count()) {
+ delta = -first_tab_y_offset_;
+ } else {
+ delta = initial_y - ideal_bounds(first_vis_index + 1).y();
+ }
+ }
+ SetFirstTabYOffset(first_tab_y_offset_ + delta);
}
BaseTab* SideTabStrip::CreateTab() {
@@ -162,7 +298,7 @@ void SideTabStrip::GenerateIdealBounds() {
gfx::Rect layout_rect = GetContentsBounds();
layout_rect.Inset(kTabStripInset, kTabStripInset);
- int y = layout_rect.y();
+ int y = layout_rect.y() + first_tab_y_offset_;
bool last_was_mini = true;
bool has_non_closing_tab = false;
separator_bounds_.SetRect(0, -kSeparatorHeight, width(), kSeparatorHeight);
@@ -196,7 +332,14 @@ void SideTabStrip::GenerateIdealBounds() {
newtab_button_bounds_ =
gfx::Rect(layout_rect.x(), y, layout_rect.width(),
newtab_button_->GetPreferredSize().height());
+ y += newtab_button_->GetPreferredSize().height();
}
+
+ ideal_height_ = y - layout_rect.y() - first_tab_y_offset_;
+
+ scroll_up_button_->SetEnabled(first_tab_y_offset_ != 0);
+ scroll_down_button_->SetEnabled(GetMaxOffset() != 0 &&
+ first_tab_y_offset_ != GetMaxOffset());
}
void SideTabStrip::StartInsertTabAnimation(int model_index) {
@@ -232,10 +375,13 @@ void SideTabStrip::AnimateToIdealBounds() {
void SideTabStrip::DoLayout() {
BaseTabStrip::DoLayout();
-
newtab_button_->SetBoundsRect(newtab_button_bounds_);
-
separator_->SetBoundsRect(separator_bounds_);
+ int scroll_button_y = height() - kScrollButtonHeight;
+ scroll_up_button_->SetBounds(0, scroll_button_y, width() / 2,
+ kScrollButtonHeight);
+ scroll_down_button_->SetBounds(width() / 2, scroll_button_y, width() / 2,
+ kScrollButtonHeight);
}
void SideTabStrip::LayoutDraggedTabsAt(const std::vector<BaseTab*>& tabs,
@@ -271,3 +417,55 @@ void SideTabStrip::CalculateBoundsForDraggedTabs(
int SideTabStrip::GetSizeNeededForTabs(const std::vector<BaseTab*>& tabs) {
return static_cast<int>(tabs.size()) * SideTab::GetPreferredHeight();
}
+
+void SideTabStrip::OnBoundsChanged(const gfx::Rect& previous_bounds) {
+ // When our height changes we may be able to show more.
+ first_tab_y_offset_ = std::max(GetMaxOffset(),
+ std::min(0, first_tab_y_offset_));
+ for (int i = 0; i < controller()->GetCount(); ++i) {
+ if (controller()->IsActiveTab(i)) {
+ MakeTabVisible(ModelIndexToTabIndex(i));
+ break;
+ }
+ }
+}
+
+void SideTabStrip::SetFirstTabYOffset(int new_offset) {
+ int max_offset = GetMaxOffset();
+ if (max_offset == 0) {
+ // All the tabs fit, no need to scroll.
+ return;
+ }
+ new_offset = std::max(max_offset, std::min(0, new_offset));
+ if (new_offset == first_tab_y_offset_)
+ return;
+
+ StopAnimating(false);
+ first_tab_y_offset_ = new_offset;
+ GenerateIdealBounds();
+ DoLayout();
+
+}
+
+int SideTabStrip::GetMaxOffset() const {
+ int available_height = GetMaxTabY() - kTabStripInset;
+ return std::min(0, available_height - ideal_height_);
+}
+
+int SideTabStrip::GetMaxTabY() const {
+ return height() - kTabStripInset - kScrollButtonVerticalPadding -
+ kScrollButtonHeight;
+}
+
+void SideTabStrip::MakeTabVisible(int tab_index) {
+ if (height() == 0)
+ return;
+
+ if (ideal_bounds(tab_index).y() < kTabStripInset) {
+ SetFirstTabYOffset(first_tab_y_offset_ - ideal_bounds(tab_index).y() +
+ kTabStripInset);
+ } else if (ideal_bounds(tab_index).bottom() > GetMaxTabY()) {
+ SetFirstTabYOffset(GetMaxTabY() - (ideal_bounds(tab_index).bottom() -
+ first_tab_y_offset_));
+ }
+}
diff --git a/chrome/browser/ui/views/tabs/side_tab_strip.h b/chrome/browser/ui/views/tabs/side_tab_strip.h
index 9c40382..54365ad 100644
--- a/chrome/browser/ui/views/tabs/side_tab_strip.h
+++ b/chrome/browser/ui/views/tabs/side_tab_strip.h
@@ -7,10 +7,11 @@
#pragma once
#include "chrome/browser/ui/views/tabs/base_tab_strip.h"
+#include "views/controls/button/button.h"
struct TabRendererData;
-class SideTabStrip : public BaseTabStrip {
+class SideTabStrip : public BaseTabStrip, public views::ButtonListener {
public:
// The tabs are inset by this much along all axis.
static const int kTabStripInset;
@@ -33,6 +34,12 @@ class SideTabStrip : public BaseTabStrip {
// views::View overrides:
virtual gfx::Size GetPreferredSize() OVERRIDE;
virtual void PaintChildren(gfx::Canvas* canvas) OVERRIDE;
+ virtual views::View* GetEventHandlerForPoint(
+ const gfx::Point& point) OVERRIDE;
+
+ // views::ButtonListener overrides:
+ virtual void ButtonPressed(views::Button* sender,
+ const views::Event& event) OVERRIDE;
protected:
// BaseTabStrip overrides:
@@ -51,13 +58,32 @@ class SideTabStrip : public BaseTabStrip {
std::vector<gfx::Rect>* bounds) OVERRIDE;
virtual int GetSizeNeededForTabs(const std::vector<BaseTab*>& tabs) OVERRIDE;
+ // views::View protected overrides:
+ virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;
+
private:
+ // Sets |first_tab_y_offset_|. This ensures |new_offset| is legal.
+ void SetFirstTabYOffset(int new_offset);
+
+ // Returns the max y offset.
+ int GetMaxOffset() const;
+
+ // Returns the max visible y-coordinate for tabs.
+ int GetMaxTabY() const;
+
+ // Make sure the tab at |tab_index| is visible.
+ void MakeTabVisible(int tab_index);
+
// The "New Tab" button.
views::View* newtab_button_;
// Ideal bounds of the new tab button.
gfx::Rect newtab_button_bounds_;
+ // Scroll buttons.
+ views::View* scroll_up_button_;
+ views::View* scroll_down_button_;
+
// Separator between mini-tabs and the new tab button. The separator is
// positioned above the visible area if there are no mini-tabs.
views::View* separator_;
@@ -65,6 +91,14 @@ class SideTabStrip : public BaseTabStrip {
// Bounds of the sepatator.
gfx::Rect separator_bounds_;
+ // Offset the first tab (or new tab button) is positioned at. If the user has
+ // scrolled the tabs this is non-zero.
+ int first_tab_y_offset_;
+
+ // Height needed to display the tabs, separator and new tab button. Doesn't
+ // include any padding.
+ int ideal_height_;
+
DISALLOW_COPY_AND_ASSIGN(SideTabStrip);
};