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