• 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/touch/tabs/touch_tab_strip.h"
6 
7 #include <algorithm>
8 #include <cmath>
9 
10 #include "chrome/browser/ui/touch/tabs/touch_tab.h"
11 #include "chrome/browser/ui/view_ids.h"
12 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
13 #include "ui/gfx/canvas_skia.h"
14 #include "views/metrics.h"
15 #include "views/window/non_client_view.h"
16 #include "views/window/window.h"
17 
18 static const int kTouchTabStripHeight = 64;
19 static const int kTouchTabWidth = 64;
20 static const int kTouchTabHeight = 64;
21 static const int kScrollThreshold = 4;
22 
TouchTabStrip(TabStripController * controller)23 TouchTabStrip::TouchTabStrip(TabStripController* controller)
24     : BaseTabStrip(controller, BaseTabStrip::HORIZONTAL_TAB_STRIP),
25       in_tab_close_(false),
26       last_tap_time_(base::Time::FromInternalValue(0)),
27       last_tapped_view_(NULL),
28       initial_mouse_x_(0),
29       initial_scroll_offset_(0),
30       scroll_offset_(0),
31       scrolling_(false),
32       initial_tab_(NULL),
33       min_scroll_offset_(0) {
34   Init();
35 }
36 
~TouchTabStrip()37 TouchTabStrip::~TouchTabStrip() {
38   // The animations may reference the tabs. Shut down the animation before we
39   // delete the tabs.
40   StopAnimating(false);
41 
42   DestroyDragController();
43 
44   // The children (tabs) may callback to us from their destructor. Delete them
45   // so that if they call back we aren't in a weird state.
46   RemoveAllChildViews(true);
47 }
48 
49 ////////////////////////////////////////////////////////////////////////////////
50 // TouchTabStrip, AbstractTabStripView implementation:
51 
IsPositionInWindowCaption(const gfx::Point & point)52 bool TouchTabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
53   // The entire tabstrip is mine. No part of it belongs to the window caption.
54   return false;
55 }
56 
SetBackgroundOffset(const gfx::Point & offset)57 void TouchTabStrip::SetBackgroundOffset(const gfx::Point& offset) {
58   for (int i = 0; i < tab_count(); ++i)
59     GetTabAtTabDataIndex(i)->set_background_offset(offset);
60 }
61 
62 ////////////////////////////////////////////////////////////////////////////////
63 // TouchTabStrip, BaseTabStrip implementation:
64 
PrepareForCloseAt(int model_index)65 void TouchTabStrip::PrepareForCloseAt(int model_index) {
66   if (!in_tab_close_ && IsAnimating()) {
67     // Cancel any current animations. We do this as remove uses the current
68     // ideal bounds and we need to know ideal bounds is in a good state.
69     StopAnimating(true);
70   }
71 
72   in_tab_close_ = true;
73 }
74 
StartHighlight(int model_index)75 void TouchTabStrip::StartHighlight(int model_index) {
76 }
77 
StopAllHighlighting()78 void TouchTabStrip::StopAllHighlighting() {
79 }
80 
CreateTabForDragging()81 BaseTab* TouchTabStrip::CreateTabForDragging() {
82   return NULL;
83 }
84 
RemoveTabAt(int model_index)85 void TouchTabStrip::RemoveTabAt(int model_index) {
86   StartRemoveTabAnimation(model_index);
87 }
88 
SelectTabAt(int old_model_index,int new_model_index)89 void TouchTabStrip::SelectTabAt(int old_model_index, int new_model_index) {
90   SchedulePaint();
91 }
92 
TabTitleChangedNotLoading(int model_index)93 void TouchTabStrip::TabTitleChangedNotLoading(int model_index) {
94 }
95 
CreateTab()96 BaseTab* TouchTabStrip::CreateTab() {
97   return new TouchTab(this);
98 }
99 
StartInsertTabAnimation(int model_index)100 void TouchTabStrip::StartInsertTabAnimation(int model_index) {
101   PrepareForAnimation();
102 
103   in_tab_close_ = false;
104 
105   GenerateIdealBounds();
106 
107   int tab_data_index = ModelIndexToTabIndex(model_index);
108   BaseTab* tab = base_tab_at_tab_index(tab_data_index);
109   if (model_index == 0) {
110     tab->SetBounds(0, ideal_bounds(tab_data_index).y(), 0,
111                    ideal_bounds(tab_data_index).height());
112   } else {
113     BaseTab* last_tab = base_tab_at_tab_index(tab_data_index - 1);
114     tab->SetBounds(last_tab->bounds().right(),
115                    ideal_bounds(tab_data_index).y(), 0,
116                    ideal_bounds(tab_data_index).height());
117   }
118 
119   AnimateToIdealBounds();
120 }
121 
AnimateToIdealBounds()122 void TouchTabStrip::AnimateToIdealBounds() {
123   for (int i = 0; i < tab_count(); ++i) {
124     TouchTab* tab = GetTabAtTabDataIndex(i);
125     if (!tab->closing() && !tab->dragging())
126       bounds_animator().AnimateViewTo(tab, ideal_bounds(i));
127   }
128 }
129 
ShouldHighlightCloseButtonAfterRemove()130 bool TouchTabStrip::ShouldHighlightCloseButtonAfterRemove() {
131   return in_tab_close_;
132 }
133 
GenerateIdealBounds()134 void TouchTabStrip::GenerateIdealBounds() {
135   gfx::Rect bounds;
136   int tab_x = 0;
137   int tab_y = 0;
138   for (int i = 0; i < tab_count(); ++i) {
139     TouchTab* tab = GetTabAtTabDataIndex(i);
140     if (!tab->closing()) {
141       int x = tab_x + scroll_offset_;
142       if (tab->IsSelected()) {
143         // limit the extent to which this tab can be displaced.
144         x = std::min(std::max(0, x), width() - kTouchTabWidth);
145       }
146       set_ideal_bounds(i, gfx::Rect(x, tab_y,
147                                     kTouchTabWidth, kTouchTabHeight));
148       // offset the next tab to the right by the width of this tab
149       tab_x += kTouchTabWidth;
150     }
151   }
152   min_scroll_offset_ = std::min(0, width() - tab_x);
153 }
154 
LayoutDraggedTabsAt(const std::vector<BaseTab * > & tabs,BaseTab * active_tab,const gfx::Point & location,bool initial_drag)155 void TouchTabStrip::LayoutDraggedTabsAt(const std::vector<BaseTab*>& tabs,
156                                         BaseTab* active_tab,
157                                         const gfx::Point& location,
158                                         bool initial_drag) {
159   // Not needed as dragging isn't supported.
160   NOTIMPLEMENTED();
161 }
162 
CalculateBoundsForDraggedTabs(const std::vector<BaseTab * > & tabs,std::vector<gfx::Rect> * bounds)163 void TouchTabStrip::CalculateBoundsForDraggedTabs(
164     const std::vector<BaseTab*>& tabs,
165     std::vector<gfx::Rect>* bounds) {
166   // Not needed as dragging isn't supported.
167   NOTIMPLEMENTED();
168 }
169 
GetSizeNeededForTabs(const std::vector<BaseTab * > & tabs)170 int TouchTabStrip::GetSizeNeededForTabs(const std::vector<BaseTab*>& tabs) {
171   // Not needed as dragging isn't supported.
172   NOTIMPLEMENTED();
173   return 0;
174 }
175 
176 // TODO(wyck): Someday we might like to get a "scroll" interaction event by way
177 // of views, triggered by the gesture manager, and/or mouse scroll wheel.
178 // For now, we're just handling a single scroll with these mouse events:
179 // OnMousePressed, OnMouseDragged, and OnMouseReleased.
180 
OnMousePressed(const views::MouseEvent & event)181 bool TouchTabStrip::OnMousePressed(const views::MouseEvent& event) {
182   // When we press the mouse button, we begin a drag
183   BeginScroll(event.location());
184   return true;
185 }
186 
OnMouseDragged(const views::MouseEvent & event)187 bool TouchTabStrip::OnMouseDragged(const views::MouseEvent& event) {
188   ContinueScroll(event.location());
189   return true;
190 }
191 
OnMouseReleased(const views::MouseEvent & event)192 void TouchTabStrip::OnMouseReleased(const views::MouseEvent& event) {
193   EndScroll(event.location());
194 }
195 
OnMouseCaptureLost()196 void TouchTabStrip::OnMouseCaptureLost() {
197   CancelScroll();
198 }
199 
BeginScroll(const gfx::Point & point)200 void TouchTabStrip::BeginScroll(const gfx::Point& point ) {
201   initial_mouse_x_ = point.x();
202   initial_scroll_offset_ = scroll_offset_;
203   initial_tab_ = static_cast<TouchTab*>(GetTabAtLocal(point));
204 }
205 
ContinueScroll(const gfx::Point & point)206 void TouchTabStrip::ContinueScroll(const gfx::Point& point) {
207   int delta_x = point.x() - initial_mouse_x_;
208   if (std::abs(delta_x) > kScrollThreshold)
209     scrolling_ = true;
210   if (scrolling_)
211     ScrollTo(delta_x);
212   DoLayout();
213   SchedulePaint();
214 }
215 
EndScroll(const gfx::Point & point)216 void TouchTabStrip::EndScroll(const gfx::Point& point) {
217   int delta_x = point.x() - initial_mouse_x_;
218   if (scrolling_) {
219     scrolling_ = false;
220     ScrollTo(delta_x);
221     StopAnimating(false);
222     GenerateIdealBounds();
223     AnimateToIdealBounds();
224   } else {
225     TouchTab* tab = static_cast<TouchTab*>(GetTabAtLocal(point));
226     if (tab && tab == initial_tab_)
227       SelectTab(tab);
228     DoLayout();
229     SchedulePaint();
230   }
231   initial_tab_ = NULL;
232 }
233 
CancelScroll()234 void TouchTabStrip::CancelScroll() {
235   // Cancel the scroll by scrolling back to the initial position (deltax = 0).
236   ScrollTo(0);
237   StopAnimating(false);
238   GenerateIdealBounds();
239   AnimateToIdealBounds();
240 }
241 
ScrollTo(int delta_x)242 void TouchTabStrip::ScrollTo(int delta_x) {
243   scroll_offset_ = initial_scroll_offset_ + delta_x;
244   // Limit the scrolling here.
245   // When scrolling beyond the limits of min and max offsets, the displacement
246   // is adjusted to 25% of what would normally applied (divided by 4).
247   // Perhaps in the future, Hooke's law could be used to model more physically
248   // based spring-like behavior.
249   int max_scroll_offset = 0;  // Because there's never content to the left of 0.
250   if (scroll_offset_ > max_scroll_offset) {
251     if (scrolling_) {
252       scroll_offset_ = max_scroll_offset
253           + std::min((scroll_offset_ - max_scroll_offset) / 4,
254                      kTouchTabWidth);
255     } else {
256       scroll_offset_ = max_scroll_offset;
257     }
258   }
259   if (scroll_offset_ < min_scroll_offset_) {
260     if (scrolling_) {
261       scroll_offset_ = min_scroll_offset_
262           + std::max((scroll_offset_ - min_scroll_offset_) / 4,
263                      -kTouchTabWidth);
264     } else {
265       scroll_offset_ = min_scroll_offset_;
266     }
267   }
268 }
269 
GetTabAtTabDataIndex(int tab_data_index) const270 TouchTab* TouchTabStrip::GetTabAtTabDataIndex(int tab_data_index) const {
271   return static_cast<TouchTab*>(base_tab_at_tab_index(tab_data_index));
272 }
273 
274 ////////////////////////////////////////////////////////////////////////////////
275 // TouchTabStrip, private:
276 
Init()277 void TouchTabStrip::Init() {
278   SetID(VIEW_ID_TAB_STRIP);
279 }
280 
281 ////////////////////////////////////////////////////////////////////////////////
282 // TouchTabStrip, views::View overrides, private:
283 
GetPreferredSize()284 gfx::Size TouchTabStrip::GetPreferredSize() {
285   return gfx::Size(0, kTouchTabStripHeight);
286 }
287 
PaintChildren(gfx::Canvas * canvas)288 void TouchTabStrip::PaintChildren(gfx::Canvas* canvas) {
289   // Tabs are painted in reverse order, so they stack to the left.
290   TouchTab* selected_tab = NULL;
291   TouchTab* dragging_tab = NULL;
292 
293   for (int i = tab_count() - 1; i >= 0; --i) {
294     TouchTab* tab = GetTabAtTabDataIndex(i);
295     // We must ask the _Tab's_ model, not ourselves, because in some situations
296     // the model will be different to this object, e.g. when a Tab is being
297     // removed after its TabContents has been destroyed.
298     if (tab->dragging()) {
299       dragging_tab = tab;
300     } else if (!tab->IsSelected()) {
301       tab->Paint(canvas);
302     } else {
303       selected_tab = tab;
304     }
305   }
306 
307   if (GetWindow()->non_client_view()->UseNativeFrame()) {
308     // Make sure unselected tabs are somewhat transparent.
309     SkPaint paint;
310     paint.setColor(SkColorSetARGB(200, 255, 255, 255));
311     paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
312     paint.setStyle(SkPaint::kFill_Style);
313     canvas->DrawRectInt(0, 0, width(),
314         height() - 2,  // Visible region that overlaps the toolbar.
315         paint);
316   }
317 
318   // Paint the selected tab last, so it overlaps all the others.
319   if (selected_tab)
320     selected_tab->Paint(canvas);
321 
322   // And the dragged tab.
323   if (dragging_tab)
324     dragging_tab->Paint(canvas);
325 }
326 
OnTouchEvent(const views::TouchEvent & event)327 views::View::TouchStatus TouchTabStrip::OnTouchEvent(
328     const views::TouchEvent& event) {
329   if (event.type() != ui::ET_TOUCH_PRESSED)
330     return TOUCH_STATUS_UNKNOWN;
331 
332   views::View* view = GetEventHandlerForPoint(event.location());
333   if (view && view != this && view->GetID() != VIEW_ID_TAB)
334     return TOUCH_STATUS_UNKNOWN;
335 
336   base::TimeDelta delta = event.time_stamp() - last_tap_time_;
337 
338   if (delta.InMilliseconds() < views::GetDoubleClickInterval() &&
339       view == last_tapped_view_) {
340     // If double tapped the empty space, open a new tab. If double tapped a tab,
341     // close it.
342     if (view == this)
343       controller()->CreateNewTab();
344     else
345       CloseTab(static_cast<BaseTab*>(view));
346 
347     last_tap_time_ = base::Time::FromInternalValue(0);
348     last_tapped_view_ = NULL;
349     return TOUCH_STATUS_END;
350   }
351 
352   last_tap_time_ = event.time_stamp();
353   last_tapped_view_ = view;
354   return TOUCH_STATUS_UNKNOWN;
355 }
356 
ViewHierarchyChanged(bool is_add,View * parent,View * child)357 void TouchTabStrip::ViewHierarchyChanged(bool is_add,
358                                          View* parent,
359                                          View* child) {
360   if (!is_add && last_tapped_view_ == child)
361     last_tapped_view_ = NULL;
362 }
363