diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-08 22:40:15 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-08 22:40:15 +0000 |
commit | 5ffa75d42d901f303b10702717272f1ea83607e7 (patch) | |
tree | 0fa6adc719c98353ab51108a9e03ebf4ddfc98a6 /chrome/browser/ui | |
parent | dda0b7b7ca05ab37d35b25717d574e5c7db7cc29 (diff) | |
download | chromium_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.cc | 14 | ||||
-rw-r--r-- | chrome/browser/ui/views/tabs/base_tab_strip.h | 10 | ||||
-rw-r--r-- | chrome/browser/ui/views/tabs/side_tab_strip.cc | 206 | ||||
-rw-r--r-- | chrome/browser/ui/views/tabs/side_tab_strip.h | 36 |
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); }; |