Home | History | Annotate | Download | only in tabs
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/views/tabs/side_tab_strip.h"
      6 
      7 #include "chrome/browser/ui/view_ids.h"
      8 #include "chrome/browser/ui/views/tabs/side_tab.h"
      9 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
     10 #include "grit/generated_resources.h"
     11 #include "grit/theme_resources.h"
     12 #include "ui/base/l10n/l10n_util.h"
     13 #include "ui/base/resource/resource_bundle.h"
     14 #include "ui/gfx/canvas.h"
     15 #include "views/background.h"
     16 #include "views/controls/button/image_button.h"
     17 #include "views/controls/button/text_button.h"
     18 
     19 namespace {
     20 
     21 const int kVerticalTabSpacing = 2;
     22 const int kTabStripWidth = 140;
     23 const SkColor kBackgroundColor = SkColorSetARGB(255, 209, 220, 248);
     24 const SkColor kSeparatorColor = SkColorSetARGB(255, 151, 159, 179);
     25 
     26 // Height of the scroll buttons.
     27 const int kScrollButtonHeight = 20;
     28 
     29 // Height of the separator.
     30 const int kSeparatorHeight = 1;
     31 
     32 // Padding between tabs and scroll button.
     33 const int kScrollButtonVerticalPadding = 2;
     34 
     35 // The new tab button is rendered using a SideTab.
     36 class SideTabNewTabButton : public SideTab {
     37  public:
     38   explicit SideTabNewTabButton(TabStripController* controller);
     39 
     40   virtual bool ShouldPaintHighlight() const OVERRIDE { return false; }
     41   virtual bool IsSelected() const OVERRIDE { return false; }
     42   virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE;
     43   virtual void OnMouseReleased(const views::MouseEvent& event) OVERRIDE;
     44 
     45  private:
     46   TabStripController* controller_;
     47 
     48   DISALLOW_COPY_AND_ASSIGN(SideTabNewTabButton);
     49 };
     50 
     51 SideTabNewTabButton::SideTabNewTabButton(TabStripController* controller)
     52     : SideTab(NULL),
     53       controller_(controller) {
     54   // Never show a close button for the new tab button.
     55   close_button()->SetVisible(false);
     56   TabRendererData data;
     57   data.favicon = *ResourceBundle::GetSharedInstance().GetBitmapNamed(
     58       IDR_SIDETABS_NEW_TAB);
     59   data.title = l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE);
     60   SetData(data);
     61 }
     62 
     63 bool SideTabNewTabButton::OnMousePressed(const views::MouseEvent& event) {
     64   return true;
     65 }
     66 
     67 void SideTabNewTabButton::OnMouseReleased(const views::MouseEvent& event) {
     68   if (event.IsOnlyLeftMouseButton() && HitTest(event.location()))
     69     controller_->CreateNewTab();
     70 }
     71 
     72 // Button class used for the scroll buttons.
     73 class ScrollButton : public views::TextButton {
     74  public:
     75   enum Type {
     76     UP,
     77     DOWN
     78   };
     79 
     80   explicit ScrollButton(views::ButtonListener* listener, Type type);
     81 
     82  protected:
     83   // views::View overrides.
     84   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
     85 
     86  private:
     87   const Type type_;
     88 
     89   DISALLOW_COPY_AND_ASSIGN(ScrollButton);
     90 };
     91 
     92 ScrollButton::ScrollButton(views::ButtonListener* listener,
     93                            Type type)
     94     : views::TextButton(listener, std::wstring()),
     95       type_(type) {
     96 }
     97 
     98 void ScrollButton::OnPaint(gfx::Canvas* canvas) {
     99   TextButton::OnPaint(canvas);
    100 
    101   // Draw the arrow.
    102   SkColor arrow_color = IsEnabled() ? SK_ColorBLACK : SK_ColorGRAY;
    103   int arrow_height = 5;
    104   int x = width() / 2;
    105   int y = (height() - arrow_height) / 2;
    106   int delta_y = 1;
    107   if (type_ == DOWN) {
    108     delta_y = -1;
    109     y += arrow_height;
    110   }
    111   for (int i = 0; i < arrow_height; ++i, --x, y += delta_y)
    112     canvas->FillRectInt(arrow_color, x, y, (i * 2) + 1, 1);
    113 }
    114 
    115 }  // namespace
    116 
    117 // static
    118 const int SideTabStrip::kTabStripInset = 3;
    119 
    120 ////////////////////////////////////////////////////////////////////////////////
    121 // SideTabStrip, public:
    122 
    123 SideTabStrip::SideTabStrip(TabStripController* controller)
    124     : BaseTabStrip(controller, BaseTabStrip::VERTICAL_TAB_STRIP),
    125       newtab_button_(new SideTabNewTabButton(controller)),
    126       scroll_up_button_(NULL),
    127       scroll_down_button_(NULL),
    128       separator_(new views::View()),
    129       first_tab_y_offset_(0),
    130       ideal_height_(0) {
    131   SetID(VIEW_ID_TAB_STRIP);
    132   set_background(views::Background::CreateSolidBackground(kBackgroundColor));
    133   AddChildView(newtab_button_);
    134   separator_->set_background(
    135       views::Background::CreateSolidBackground(kSeparatorColor));
    136   AddChildView(separator_);
    137   scroll_up_button_ = new ScrollButton(this, ScrollButton::UP);
    138   AddChildView(scroll_up_button_);
    139   scroll_down_button_ = new ScrollButton(this, ScrollButton::DOWN);
    140   AddChildView(scroll_down_button_);
    141 }
    142 
    143 SideTabStrip::~SideTabStrip() {
    144   DestroyDragController();
    145 }
    146 
    147 ////////////////////////////////////////////////////////////////////////////////
    148 // SideTabStrip, AbstractTabStripView implementation:
    149 
    150 bool SideTabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
    151   return GetEventHandlerForPoint(point) == this;
    152 }
    153 
    154 void SideTabStrip::SetBackgroundOffset(const gfx::Point& offset) {
    155 }
    156 
    157 ////////////////////////////////////////////////////////////////////////////////
    158 // SideTabStrip, BaseTabStrip implementation:
    159 
    160 void SideTabStrip::StartHighlight(int model_index) {
    161 }
    162 
    163 void SideTabStrip::StopAllHighlighting() {
    164 }
    165 
    166 BaseTab* SideTabStrip::CreateTabForDragging() {
    167   SideTab* tab = new SideTab(NULL);
    168   // Make sure the dragged tab shares our theme provider. We need to explicitly
    169   // do this as during dragging there isn't a theme provider.
    170   tab->set_theme_provider(GetThemeProvider());
    171   return tab;
    172 }
    173 
    174 void SideTabStrip::RemoveTabAt(int model_index) {
    175   StartRemoveTabAnimation(model_index);
    176 }
    177 
    178 void SideTabStrip::SelectTabAt(int old_model_index, int new_model_index) {
    179   GetBaseTabAtModelIndex(new_model_index)->SchedulePaint();
    180 
    181   if (controller()->IsActiveTab(new_model_index))
    182     MakeTabVisible(ModelIndexToTabIndex(new_model_index));
    183 }
    184 
    185 void SideTabStrip::TabTitleChangedNotLoading(int model_index) {
    186 }
    187 
    188 gfx::Size SideTabStrip::GetPreferredSize() {
    189   return gfx::Size(kTabStripWidth, 0);
    190 }
    191 
    192 void SideTabStrip::PaintChildren(gfx::Canvas* canvas) {
    193   // Make sure any tabs being dragged appear on top of all others by painting
    194   // them last.
    195   std::vector<BaseTab*> dragging_tabs;
    196 
    197   // Make sure nothing draws on top of the scroll buttons.
    198   canvas->Save();
    199   canvas->ClipRectInt(kTabStripInset, kTabStripInset,
    200                       width() - kTabStripInset - kTabStripInset,
    201                       GetMaxTabY() - kTabStripInset);
    202 
    203   // Paint the new tab and separator first so that any tabs animating appear on
    204   // top.
    205   separator_->Paint(canvas);
    206   newtab_button_->Paint(canvas);
    207 
    208   for (int i = tab_count() - 1; i >= 0; --i) {
    209     BaseTab* tab = base_tab_at_tab_index(i);
    210     if (tab->dragging())
    211       dragging_tabs.push_back(tab);
    212     else
    213       tab->Paint(canvas);
    214   }
    215 
    216   for (size_t i = 0; i < dragging_tabs.size(); ++i)
    217     dragging_tabs[i]->Paint(canvas);
    218 
    219   canvas->Restore();
    220 
    221   scroll_down_button_->Paint(canvas);
    222   scroll_up_button_->Paint(canvas);
    223 }
    224 
    225 views::View* SideTabStrip::GetEventHandlerForPoint(const gfx::Point& point) {
    226   // Check the scroll buttons first as they visually appear on top of everything
    227   // else.
    228   if (scroll_down_button_->IsVisible()) {
    229     gfx::Point local_point(point);
    230     View::ConvertPointToView(this, scroll_down_button_, &local_point);
    231     if (scroll_down_button_->HitTest(local_point))
    232       return scroll_down_button_->GetEventHandlerForPoint(local_point);
    233   }
    234 
    235   if (scroll_up_button_->IsVisible()) {
    236     gfx::Point local_point(point);
    237     View::ConvertPointToView(this, scroll_up_button_, &local_point);
    238     if (scroll_up_button_->HitTest(local_point))
    239       return scroll_up_button_->GetEventHandlerForPoint(local_point);
    240   }
    241   return views::View::GetEventHandlerForPoint(point);
    242 }
    243 
    244 void SideTabStrip::ButtonPressed(views::Button* sender,
    245                                  const views::Event& event) {
    246   int max_offset = GetMaxOffset();
    247   if (max_offset == 0) {
    248     // All the tabs fit, no need to scroll.
    249     return;
    250   }
    251 
    252   // Determine the index of the first visible tab.
    253   int initial_y = kTabStripInset;
    254   int first_vis_index = -1;
    255   for (int i = 0; i < tab_count(); ++i) {
    256     if (ideal_bounds(i).bottom() > initial_y) {
    257       first_vis_index = i;
    258       break;
    259     }
    260   }
    261   if (first_vis_index == -1)
    262     return;
    263 
    264   int delta = 0;
    265   if (sender == scroll_up_button_) {
    266     delta = initial_y - ideal_bounds(first_vis_index).y();
    267     if (delta <= 0) {
    268       if (first_vis_index == 0) {
    269         delta = -first_tab_y_offset_;
    270       } else {
    271         delta = initial_y - ideal_bounds(first_vis_index - 1).y();
    272         DCHECK_NE(0, delta);  // Not fatal, but indicates we aren't scrolling.
    273       }
    274     }
    275   } else {
    276     DCHECK_EQ(sender, scroll_down_button_);
    277     if (ideal_bounds(first_vis_index).y() > initial_y) {
    278       delta = initial_y - ideal_bounds(first_vis_index).y();
    279     } else if (first_vis_index + 1 == tab_count()) {
    280       delta = -first_tab_y_offset_;
    281     } else {
    282       delta = initial_y - ideal_bounds(first_vis_index + 1).y();
    283     }
    284   }
    285   SetFirstTabYOffset(first_tab_y_offset_ + delta);
    286 }
    287 
    288 BaseTab* SideTabStrip::CreateTab() {
    289   return new SideTab(this);
    290 }
    291 
    292 void SideTabStrip::GenerateIdealBounds() {
    293   gfx::Rect layout_rect = GetContentsBounds();
    294   layout_rect.Inset(kTabStripInset, kTabStripInset);
    295 
    296   int y = layout_rect.y() + first_tab_y_offset_;
    297   bool last_was_mini = true;
    298   bool has_non_closing_tab = false;
    299   separator_bounds_.SetRect(0, -kSeparatorHeight, width(), kSeparatorHeight);
    300   for (int i = 0; i < tab_count(); ++i) {
    301     BaseTab* tab = base_tab_at_tab_index(i);
    302     if (!tab->closing()) {
    303       if (last_was_mini != tab->data().mini) {
    304         if (has_non_closing_tab) {
    305           separator_bounds_.SetRect(0, y, width(), kSeparatorHeight);
    306           y += kSeparatorHeight + kVerticalTabSpacing;
    307         }
    308         newtab_button_bounds_.SetRect(
    309             layout_rect.x(), y, layout_rect.width(),
    310             newtab_button_->GetPreferredSize().height());
    311         y = newtab_button_bounds_.bottom() + kVerticalTabSpacing;
    312         last_was_mini = tab->data().mini;
    313       }
    314       gfx::Rect bounds = gfx::Rect(layout_rect.x(), y, layout_rect.width(),
    315                                    tab->GetPreferredSize().height());
    316       set_ideal_bounds(i, bounds);
    317       y = bounds.bottom() + kVerticalTabSpacing;
    318       has_non_closing_tab = true;
    319     }
    320   }
    321 
    322   if (last_was_mini) {
    323     if (has_non_closing_tab) {
    324       separator_bounds_.SetRect(0, y, width(), kSeparatorHeight);
    325       y += kSeparatorHeight + kVerticalTabSpacing;
    326     }
    327     newtab_button_bounds_ =
    328         gfx::Rect(layout_rect.x(), y, layout_rect.width(),
    329                   newtab_button_->GetPreferredSize().height());
    330     y += newtab_button_->GetPreferredSize().height();
    331   }
    332 
    333   ideal_height_ = y - layout_rect.y() - first_tab_y_offset_;
    334 
    335   scroll_up_button_->SetEnabled(first_tab_y_offset_ != 0);
    336   scroll_down_button_->SetEnabled(GetMaxOffset() != 0 &&
    337                                   first_tab_y_offset_ != GetMaxOffset());
    338 }
    339 
    340 void SideTabStrip::StartInsertTabAnimation(int model_index) {
    341   PrepareForAnimation();
    342 
    343   GenerateIdealBounds();
    344 
    345   int tab_data_index = ModelIndexToTabIndex(model_index);
    346   BaseTab* tab = base_tab_at_tab_index(tab_data_index);
    347   if (model_index == 0) {
    348     tab->SetBounds(ideal_bounds(tab_data_index).x(), 0,
    349                    ideal_bounds(tab_data_index).width(), 0);
    350   } else {
    351     BaseTab* last_tab = base_tab_at_tab_index(tab_data_index - 1);
    352     tab->SetBounds(last_tab->x(), last_tab->bounds().bottom(),
    353                    ideal_bounds(tab_data_index).width(), 0);
    354   }
    355 
    356   AnimateToIdealBounds();
    357 }
    358 
    359 void SideTabStrip::AnimateToIdealBounds() {
    360   for (int i = 0; i < tab_count(); ++i) {
    361     BaseTab* tab = base_tab_at_tab_index(i);
    362     if (!tab->closing() && !tab->dragging())
    363       bounds_animator().AnimateViewTo(tab, ideal_bounds(i));
    364   }
    365 
    366   bounds_animator().AnimateViewTo(newtab_button_, newtab_button_bounds_);
    367 
    368   bounds_animator().AnimateViewTo(separator_, separator_bounds_);
    369 }
    370 
    371 void SideTabStrip::DoLayout() {
    372   BaseTabStrip::DoLayout();
    373   newtab_button_->SetBoundsRect(newtab_button_bounds_);
    374   separator_->SetBoundsRect(separator_bounds_);
    375   int scroll_button_y = height() - kScrollButtonHeight;
    376   scroll_up_button_->SetBounds(0, scroll_button_y, width() / 2,
    377                                kScrollButtonHeight);
    378   scroll_down_button_->SetBounds(width() / 2, scroll_button_y, width() / 2,
    379                                  kScrollButtonHeight);
    380 }
    381 
    382 void SideTabStrip::LayoutDraggedTabsAt(const std::vector<BaseTab*>& tabs,
    383                                        BaseTab* active_tab,
    384                                        const gfx::Point& location,
    385                                        bool initial_drag) {
    386   // TODO: add support for initial_drag (see TabStrip's implementation).
    387   gfx::Rect layout_rect = GetContentsBounds();
    388   layout_rect.Inset(kTabStripInset, kTabStripInset);
    389   int y = location.y();
    390   for (size_t i = 0; i < tabs.size(); ++i) {
    391     BaseTab* tab = tabs[i];
    392     tab->SchedulePaint();
    393     tab->SetBounds(layout_rect.x(), y, layout_rect.width(),
    394                    tab->GetPreferredSize().height());
    395     tab->SchedulePaint();
    396     y += tab->height() + kVerticalTabSpacing;
    397   }
    398 }
    399 
    400 void SideTabStrip::CalculateBoundsForDraggedTabs(
    401     const std::vector<BaseTab*>& tabs,
    402     std::vector<gfx::Rect>* bounds) {
    403   int y = 0;
    404   for (size_t i = 0; i < tabs.size(); ++i) {
    405     BaseTab* tab = tabs[i];
    406     gfx::Rect tab_bounds(tab->bounds());
    407     tab_bounds.set_y(y);
    408     y += tab->height() + kVerticalTabSpacing;
    409     bounds->push_back(tab_bounds);
    410   }
    411 }
    412 
    413 int SideTabStrip::GetSizeNeededForTabs(const std::vector<BaseTab*>& tabs) {
    414   return static_cast<int>(tabs.size()) * SideTab::GetPreferredHeight();
    415 }
    416 
    417 void SideTabStrip::OnBoundsChanged(const gfx::Rect& previous_bounds) {
    418   // When our height changes we may be able to show more.
    419   first_tab_y_offset_ = std::max(GetMaxOffset(),
    420                                  std::min(0, first_tab_y_offset_));
    421   for (int i = 0; i < controller()->GetCount(); ++i) {
    422     if (controller()->IsActiveTab(i)) {
    423       MakeTabVisible(ModelIndexToTabIndex(i));
    424       break;
    425     }
    426   }
    427 }
    428 
    429 void SideTabStrip::SetFirstTabYOffset(int new_offset) {
    430   int max_offset = GetMaxOffset();
    431   if (max_offset == 0) {
    432     // All the tabs fit, no need to scroll.
    433     return;
    434   }
    435   new_offset = std::max(max_offset, std::min(0, new_offset));
    436   if (new_offset == first_tab_y_offset_)
    437     return;
    438 
    439   StopAnimating(false);
    440   first_tab_y_offset_ = new_offset;
    441   GenerateIdealBounds();
    442   DoLayout();
    443 
    444 }
    445 
    446 int SideTabStrip::GetMaxOffset() const {
    447   int available_height = GetMaxTabY() - kTabStripInset;
    448   return std::min(0, available_height - ideal_height_);
    449 }
    450 
    451 int SideTabStrip::GetMaxTabY() const {
    452   return height() - kTabStripInset - kScrollButtonVerticalPadding -
    453       kScrollButtonHeight;
    454 }
    455 
    456 void SideTabStrip::MakeTabVisible(int tab_index) {
    457   if (height() == 0)
    458     return;
    459 
    460   if (ideal_bounds(tab_index).y() < kTabStripInset) {
    461     SetFirstTabYOffset(first_tab_y_offset_ - ideal_bounds(tab_index).y() +
    462                        kTabStripInset);
    463   } else if (ideal_bounds(tab_index).bottom() > GetMaxTabY()) {
    464     SetFirstTabYOffset(GetMaxTabY() - (ideal_bounds(tab_index).bottom() -
    465                                        first_tab_y_offset_));
    466   }
    467 }
    468