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"
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"
26 const int64 kActivationDelayMS
= 200;
30 TabScrubber
* TabScrubber::GetInstance() {
31 static TabScrubber
* instance
= NULL
;
33 instance
= new TabScrubber();
38 gfx::Point
TabScrubber::GetStartPoint(
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
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()
64 swipe_direction_(LEFT
),
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);
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
) {
87 immersive_reveal_lock_
.reset();
91 if (event
->finger_count() != 3)
94 Browser
* browser
= GetActiveBrowser();
95 if (!browser
|| (scrubbing_
&& browser_
&& browser
!= browser_
) ||
96 (highlighted_tab_
!= -1 &&
97 highlighted_tab_
>= browser
->tab_strip_model()->count())) {
102 BrowserView
* browser_view
=
103 BrowserView::GetBrowserViewForNativeWindow(
104 browser
->window()->GetNativeWindow());
105 TabStrip
* tab_strip
= browser_view
->tabstrip();
107 if (tab_strip
->IsAnimating()) {
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()
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
);
139 int new_index
= tab_strip_
->GetModelIndexOfTab(new_tab
);
140 if (highlighted_tab_
== -1 &&
141 new_index
== browser_
->tab_strip_model()->active_index()) {
145 if (new_index
!= highlighted_tab_
) {
146 if (activate_timer_
.IsRunning())
147 activate_timer_
.Reset();
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();
169 highlighted_tab_
= -1;
171 tab_strip_
= nullptr;
175 void TabScrubber::TabStripAddedTabAt(TabStrip
* tab_strip
, int index
) {
176 if (highlighted_tab_
== -1)
179 if (index
< highlighted_tab_
)
183 void TabScrubber::TabStripMovedTab(TabStrip
* tab_strip
,
186 if (highlighted_tab_
== -1)
189 if (from_index
== highlighted_tab_
)
190 highlighted_tab_
= to_index
;
191 else if (from_index
< highlighted_tab_
&& highlighted_tab_
<= to_index
)
193 else if (from_index
> highlighted_tab_
&& highlighted_tab_
>= to_index
)
197 void TabScrubber::TabStripRemovedTabAt(TabStrip
* tab_strip
, int index
) {
198 if (highlighted_tab_
== -1)
200 if (index
== highlighted_tab_
) {
204 if (index
< highlighted_tab_
)
208 void TabScrubber::TabStripDeleted(TabStrip
* tab_strip
) {
209 if (highlighted_tab_
== -1)
213 Browser
* TabScrubber::GetActiveBrowser() {
214 aura::Window
* active_window
= ash::wm::GetActiveWindow();
218 Browser
* browser
= chrome::FindBrowserWithWindow(active_window
);
219 if (!browser
|| browser
->type() != Browser::TYPE_TABBED
)
225 void TabScrubber::BeginScrub(Browser
* browser
,
226 BrowserView
* browser_view
,
229 DCHECK(browser_view
);
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();
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);
269 tab_strip_
= nullptr;
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()
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
) {
294 swipe_direction_
= direction
;
295 const gfx::Point start_point
=
296 GetStartPoint(tab_strip_
, browser_
->tab_strip_model()->active_index(),
298 swipe_x_
= start_point
.x();
299 swipe_y_
= start_point
.y();
302 void TabScrubber::UpdateSwipeX(float x_offset
) {
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
) {
330 if (new_index
== highlighted_tab_
)
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
);
342 highlighted_tab_
= -1;