Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / views / ash / tab_scrubber.cc
bloba3583dd8858cdc1727263c199537697bb5cccb6f
1 // Copyright (c) 2013 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.
5 #include "chrome/browser/ui/views/ash/tab_scrubber.h"
7 #include "ash/shell.h"
8 #include "ash/wm/window_util.h"
9 #include "base/metrics/histogram.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/ui/browser.h"
12 #include "chrome/browser/ui/browser_finder.h"
13 #include "chrome/browser/ui/tabs/tab_strip_model.h"
14 #include "chrome/browser/ui/views/frame/browser_view.h"
15 #include "chrome/browser/ui/views/tabs/tab.h"
16 #include "chrome/browser/ui/views/tabs/tab_strip.h"
17 #include "content/public/browser/notification_service.h"
18 #include "content/public/browser/notification_source.h"
19 #include "ui/aura/window.h"
20 #include "ui/events/event.h"
21 #include "ui/events/event_utils.h"
22 #include "ui/events/gesture_detection/gesture_configuration.h"
23 #include "ui/views/controls/glow_hover_controller.h"
25 namespace {
26 const int64 kActivationDelayMS = 200;
29 // static
30 TabScrubber* TabScrubber::GetInstance() {
31 static TabScrubber* instance = NULL;
32 if (!instance)
33 instance = new TabScrubber();
34 return instance;
37 // static
38 gfx::Point TabScrubber::GetStartPoint(
39 TabStrip* tab_strip,
40 int index,
41 TabScrubber::Direction direction) {
42 int initial_tab_offset = Tab::GetPinnedWidth() / 2;
43 // In RTL layouts the tabs are mirrored. We hence use GetMirroredBounds()
44 // which will give us the correct bounds of tabs in RTL layouts as well as
45 // non-RTL layouts (in non-RTL layouts GetMirroredBounds() is the same as
46 // bounds()).
47 gfx::Rect tab_bounds = tab_strip->tab_at(index)->GetMirroredBounds();
48 float x = direction == LEFT ?
49 tab_bounds.x() + initial_tab_offset :
50 tab_bounds.right() - initial_tab_offset;
51 return gfx::Point(x, tab_bounds.CenterPoint().y());
54 bool TabScrubber::IsActivationPending() {
55 return activate_timer_.IsRunning();
58 TabScrubber::TabScrubber()
59 : scrubbing_(false),
60 browser_(nullptr),
61 tab_strip_(nullptr),
62 swipe_x_(-1),
63 swipe_y_(-1),
64 swipe_direction_(LEFT),
65 highlighted_tab_(-1),
66 activate_timer_(true, false),
67 activation_delay_(kActivationDelayMS),
68 use_default_activation_delay_(true),
69 weak_ptr_factory_(this) {
70 ash::Shell::GetInstance()->AddPreTargetHandler(this);
71 registrar_.Add(
72 this,
73 chrome::NOTIFICATION_BROWSER_CLOSED,
74 content::NotificationService::AllSources());
77 TabScrubber::~TabScrubber() {
78 // Note: The weak_ptr_factory_ should invalidate its weak pointers before
79 // any other members are destroyed.
80 weak_ptr_factory_.InvalidateWeakPtrs();
83 void TabScrubber::OnScrollEvent(ui::ScrollEvent* event) {
84 if (event->type() == ui::ET_SCROLL_FLING_CANCEL ||
85 event->type() == ui::ET_SCROLL_FLING_START) {
86 FinishScrub(true);
87 immersive_reveal_lock_.reset();
88 return;
91 if (event->finger_count() != 3)
92 return;
94 Browser* browser = GetActiveBrowser();
95 if (!browser || (scrubbing_ && browser_ && browser != browser_) ||
96 (highlighted_tab_ != -1 &&
97 highlighted_tab_ >= browser->tab_strip_model()->count())) {
98 FinishScrub(false);
99 return;
102 BrowserView* browser_view =
103 BrowserView::GetBrowserViewForNativeWindow(
104 browser->window()->GetNativeWindow());
105 TabStrip* tab_strip = browser_view->tabstrip();
107 if (tab_strip->IsAnimating()) {
108 FinishScrub(false);
109 return;
112 // We are handling the event.
113 event->StopPropagation();
115 // The event's x_offset doesn't change in an RTL layout. Negative value means
116 // left, positive means right.
117 float x_offset = event->x_offset();
118 int initial_tab_index = highlighted_tab_ == -1
119 ? browser->tab_strip_model()->active_index()
120 : highlighted_tab_;
121 if (!scrubbing_) {
122 BeginScrub(browser, browser_view, x_offset);
123 } else if (highlighted_tab_ == -1) {
124 // Has the direction of the swipe changed while scrubbing?
125 Direction direction = (x_offset < 0) ? LEFT : RIGHT;
126 if (direction != swipe_direction_)
127 ScrubDirectionChanged(direction);
130 UpdateSwipeX(x_offset);
132 Tab* initial_tab = tab_strip_->tab_at(initial_tab_index);
133 gfx::Point tab_point(swipe_x_, swipe_y_);
134 views::View::ConvertPointToTarget(tab_strip_, initial_tab, &tab_point);
135 Tab* new_tab = tab_strip_->GetTabAt(initial_tab, tab_point);
136 if (!new_tab)
137 return;
139 int new_index = tab_strip_->GetModelIndexOfTab(new_tab);
140 if (highlighted_tab_ == -1 &&
141 new_index == browser_->tab_strip_model()->active_index()) {
142 return;
145 if (new_index != highlighted_tab_) {
146 if (activate_timer_.IsRunning())
147 activate_timer_.Reset();
148 else
149 ScheduleFinishScrubIfNeeded();
152 UpdateHighlightedTab(new_tab, new_index);
154 if (highlighted_tab_ != -1) {
155 gfx::Point hover_point(swipe_x_, swipe_y_);
156 views::View::ConvertPointToTarget(tab_strip_, new_tab, &hover_point);
157 new_tab->hover_controller()->SetLocation(hover_point);
161 void TabScrubber::Observe(int type,
162 const content::NotificationSource& source,
163 const content::NotificationDetails& details) {
164 if (content::Source<Browser>(source).ptr() == browser_) {
165 activate_timer_.Stop();
166 swipe_x_ = -1;
167 swipe_y_ = -1;
168 scrubbing_ = false;
169 highlighted_tab_ = -1;
170 browser_ = nullptr;
171 tab_strip_ = nullptr;
175 void TabScrubber::TabStripAddedTabAt(TabStrip* tab_strip, int index) {
176 if (highlighted_tab_ == -1)
177 return;
179 if (index < highlighted_tab_)
180 ++highlighted_tab_;
183 void TabScrubber::TabStripMovedTab(TabStrip* tab_strip,
184 int from_index,
185 int to_index) {
186 if (highlighted_tab_ == -1)
187 return;
189 if (from_index == highlighted_tab_)
190 highlighted_tab_ = to_index;
191 else if (from_index < highlighted_tab_&& highlighted_tab_<= to_index)
192 --highlighted_tab_;
193 else if (from_index > highlighted_tab_ && highlighted_tab_ >= to_index)
194 ++highlighted_tab_;
197 void TabScrubber::TabStripRemovedTabAt(TabStrip* tab_strip, int index) {
198 if (highlighted_tab_ == -1)
199 return;
200 if (index == highlighted_tab_) {
201 FinishScrub(false);
202 return;
204 if (index < highlighted_tab_)
205 --highlighted_tab_;
208 void TabScrubber::TabStripDeleted(TabStrip* tab_strip) {
209 if (highlighted_tab_ == -1)
210 return;
213 Browser* TabScrubber::GetActiveBrowser() {
214 aura::Window* active_window = ash::wm::GetActiveWindow();
215 if (!active_window)
216 return NULL;
218 Browser* browser = chrome::FindBrowserWithWindow(active_window);
219 if (!browser || browser->type() != Browser::TYPE_TABBED)
220 return NULL;
222 return browser;
225 void TabScrubber::BeginScrub(Browser* browser,
226 BrowserView* browser_view,
227 float x_offset) {
228 DCHECK(browser);
229 DCHECK(browser_view);
231 scrubbing_ = true;
232 browser_ = browser;
233 tab_strip_ = browser_view->tabstrip();
235 Direction direction = (x_offset < 0) ? LEFT : RIGHT;
236 ScrubDirectionChanged(direction);
238 ImmersiveModeController* immersive_controller =
239 browser_view->immersive_mode_controller();
240 if (immersive_controller->IsEnabled()) {
241 immersive_reveal_lock_.reset(immersive_controller->GetRevealedLock(
242 ImmersiveModeController::ANIMATE_REVEAL_YES));
245 tab_strip_->AddObserver(this);
248 void TabScrubber::FinishScrub(bool activate) {
249 activate_timer_.Stop();
251 if (browser_ && browser_->window()) {
252 BrowserView* browser_view =
253 BrowserView::GetBrowserViewForNativeWindow(
254 browser_->window()->GetNativeWindow());
255 TabStrip* tab_strip = browser_view->tabstrip();
256 if (activate && highlighted_tab_ != -1) {
257 Tab* tab = tab_strip->tab_at(highlighted_tab_);
258 tab->hover_controller()->HideImmediately();
259 int distance =
260 std::abs(
261 highlighted_tab_ - browser_->tab_strip_model()->active_index());
262 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.ScrubDistance", distance, 0, 20, 21);
263 browser_->tab_strip_model()->ActivateTabAt(highlighted_tab_, true);
265 tab_strip->RemoveObserver(this);
268 browser_ = nullptr;
269 tab_strip_ = nullptr;
270 swipe_x_ = -1;
271 swipe_y_ = -1;
272 scrubbing_ = false;
273 highlighted_tab_ = -1;
276 void TabScrubber::ScheduleFinishScrubIfNeeded() {
277 int delay = use_default_activation_delay_
278 ? ui::GestureConfiguration::GetInstance()
279 ->tab_scrub_activation_delay_in_ms()
280 : activation_delay_;
282 if (delay >= 0) {
283 activate_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(delay),
284 base::Bind(&TabScrubber::FinishScrub,
285 weak_ptr_factory_.GetWeakPtr(), true));
289 void TabScrubber::ScrubDirectionChanged(Direction direction) {
290 DCHECK(browser_);
291 DCHECK(tab_strip_);
292 DCHECK(scrubbing_);
294 swipe_direction_ = direction;
295 const gfx::Point start_point =
296 GetStartPoint(tab_strip_, browser_->tab_strip_model()->active_index(),
297 swipe_direction_);
298 swipe_x_ = start_point.x();
299 swipe_y_ = start_point.y();
302 void TabScrubber::UpdateSwipeX(float x_offset) {
303 DCHECK(browser_);
304 DCHECK(tab_strip_);
305 DCHECK(scrubbing_);
307 swipe_x_ += x_offset;
309 // In an RTL layout, everything is mirrored, i.e. the index of the first tab
310 // (with the smallest X mirrored co-ordinates) is actually the index of the
311 // last tab. Same for the index of the last tab.
312 int first_tab_index = base::i18n::IsRTL() ? tab_strip_->tab_count() - 1 : 0;
313 int last_tab_index = base::i18n::IsRTL() ? 0 : tab_strip_->tab_count() - 1;
315 Tab* first_tab = tab_strip_->tab_at(first_tab_index);
316 int first_tab_center = first_tab->GetMirroredBounds().CenterPoint().x();
317 Tab* last_tab = tab_strip_->tab_at(last_tab_index);
318 int last_tab_center = last_tab->GetMirroredBounds().CenterPoint().x();
320 if (swipe_x_ < first_tab_center)
321 swipe_x_ = first_tab_center;
322 if (swipe_x_ > last_tab_center)
323 swipe_x_ = last_tab_center;
326 void TabScrubber::UpdateHighlightedTab(Tab* new_tab, int new_index) {
327 DCHECK(scrubbing_);
328 DCHECK(new_tab);
330 if (new_index == highlighted_tab_)
331 return;
333 if (highlighted_tab_ != -1) {
334 Tab* tab = tab_strip_->tab_at(highlighted_tab_);
335 tab->hover_controller()->HideImmediately();
338 if (new_index != browser_->tab_strip_model()->active_index()) {
339 highlighted_tab_ = new_index;
340 new_tab->hover_controller()->Show(views::GlowHoverController::PRONOUNCED);
341 } else {
342 highlighted_tab_ = -1;