• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ShouldPaintHighlight() const40   virtual bool ShouldPaintHighlight() const OVERRIDE { return false; }
IsSelected() const41   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 
SideTabNewTabButton(TabStripController * controller)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 
OnMousePressed(const views::MouseEvent & event)63 bool SideTabNewTabButton::OnMousePressed(const views::MouseEvent& event) {
64   return true;
65 }
66 
OnMouseReleased(const views::MouseEvent & event)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 
ScrollButton(views::ButtonListener * listener,Type type)92 ScrollButton::ScrollButton(views::ButtonListener* listener,
93                            Type type)
94     : views::TextButton(listener, std::wstring()),
95       type_(type) {
96 }
97 
OnPaint(gfx::Canvas * canvas)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 
SideTabStrip(TabStripController * controller)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 
~SideTabStrip()143 SideTabStrip::~SideTabStrip() {
144   DestroyDragController();
145 }
146 
147 ////////////////////////////////////////////////////////////////////////////////
148 // SideTabStrip, AbstractTabStripView implementation:
149 
IsPositionInWindowCaption(const gfx::Point & point)150 bool SideTabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
151   return GetEventHandlerForPoint(point) == this;
152 }
153 
SetBackgroundOffset(const gfx::Point & offset)154 void SideTabStrip::SetBackgroundOffset(const gfx::Point& offset) {
155 }
156 
157 ////////////////////////////////////////////////////////////////////////////////
158 // SideTabStrip, BaseTabStrip implementation:
159 
StartHighlight(int model_index)160 void SideTabStrip::StartHighlight(int model_index) {
161 }
162 
StopAllHighlighting()163 void SideTabStrip::StopAllHighlighting() {
164 }
165 
CreateTabForDragging()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 
RemoveTabAt(int model_index)174 void SideTabStrip::RemoveTabAt(int model_index) {
175   StartRemoveTabAnimation(model_index);
176 }
177 
SelectTabAt(int old_model_index,int new_model_index)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 
TabTitleChangedNotLoading(int model_index)185 void SideTabStrip::TabTitleChangedNotLoading(int model_index) {
186 }
187 
GetPreferredSize()188 gfx::Size SideTabStrip::GetPreferredSize() {
189   return gfx::Size(kTabStripWidth, 0);
190 }
191 
PaintChildren(gfx::Canvas * canvas)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 
GetEventHandlerForPoint(const gfx::Point & point)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 
ButtonPressed(views::Button * sender,const views::Event & event)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 
CreateTab()288 BaseTab* SideTabStrip::CreateTab() {
289   return new SideTab(this);
290 }
291 
GenerateIdealBounds()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 
StartInsertTabAnimation(int model_index)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 
AnimateToIdealBounds()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 
DoLayout()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 
LayoutDraggedTabsAt(const std::vector<BaseTab * > & tabs,BaseTab * active_tab,const gfx::Point & location,bool initial_drag)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 
CalculateBoundsForDraggedTabs(const std::vector<BaseTab * > & tabs,std::vector<gfx::Rect> * bounds)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 
GetSizeNeededForTabs(const std::vector<BaseTab * > & tabs)413 int SideTabStrip::GetSizeNeededForTabs(const std::vector<BaseTab*>& tabs) {
414   return static_cast<int>(tabs.size()) * SideTab::GetPreferredHeight();
415 }
416 
OnBoundsChanged(const gfx::Rect & previous_bounds)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 
SetFirstTabYOffset(int new_offset)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 
GetMaxOffset() const446 int SideTabStrip::GetMaxOffset() const {
447   int available_height = GetMaxTabY() - kTabStripInset;
448   return std::min(0, available_height - ideal_height_);
449 }
450 
GetMaxTabY() const451 int SideTabStrip::GetMaxTabY() const {
452   return height() - kTabStripInset - kScrollButtonVerticalPadding -
453       kScrollButtonHeight;
454 }
455 
MakeTabVisible(int tab_index)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