1 // Copyright 2018 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 "ui/views/animation/slide_out_controller.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "ui/views/animation/slide_out_controller_delegate.h"
11 #include "ui/views/test/views_test_base.h"
12 #include "ui/views/view.h"
13
14 namespace views {
15
16 namespace {
17 constexpr int kSwipeControlWidth = 30; // px
18 constexpr int kTargetWidth = 200; // px
19 } // namespace
20
21 class TestSlideOutControllerDelegate : public SlideOutControllerDelegate {
22 public:
TestSlideOutControllerDelegate(View * target)23 explicit TestSlideOutControllerDelegate(View* target) : target_(target) {}
24 ~TestSlideOutControllerDelegate() override = default;
25
GetSlideOutLayer()26 ui::Layer* GetSlideOutLayer() override { return target_->layer(); }
27
OnSlideStarted()28 void OnSlideStarted() override { ++slide_started_count_; }
29
OnSlideChanged(bool in_progress)30 void OnSlideChanged(bool in_progress) override {
31 slide_changed_last_value_ = in_progress;
32 ++slide_changed_count_;
33 }
34
IsOnSlideChangedCalled() const35 bool IsOnSlideChangedCalled() const { return (slide_changed_count_ > 0); }
36
OnSlideOut()37 void OnSlideOut() override { ++slide_out_count_; }
38
reset()39 void reset() {
40 slide_started_count_ = 0;
41 slide_changed_count_ = 0;
42 slide_out_count_ = 0;
43 slide_changed_last_value_ = base::nullopt;
44 }
45
46 base::Optional<bool> slide_changed_last_value_;
47 int slide_started_count_ = 0;
48 int slide_changed_count_ = 0;
49 int slide_out_count_ = 0;
50
51 private:
52 View* const target_;
53 };
54
55 class SlideOutControllerTest : public ViewsTestBase {
56 public:
57 SlideOutControllerTest() = default;
58 ~SlideOutControllerTest() override = default;
59
SetUp()60 void SetUp() override {
61 ViewsTestBase::SetUp();
62
63 widget_ = std::make_unique<Widget>();
64
65 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
66 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
67 params.bounds = gfx::Rect(50, 50, 650, 650);
68 widget_->Init(std::move(params));
69 View* root = widget_->GetRootView();
70
71 View* target_ = new View();
72 target_->SetPaintToLayer(ui::LAYER_TEXTURED);
73 target_->SetSize(gfx::Size(kTargetWidth, 50));
74
75 root->AddChildView(target_);
76 widget_->Show();
77
78 delegate_ = std::make_unique<TestSlideOutControllerDelegate>(target_);
79 slide_out_controller_ =
80 std::make_unique<SlideOutController>(target_, delegate_.get());
81 }
82
TearDown()83 void TearDown() override {
84 slide_out_controller_.reset();
85 delegate_.reset();
86 widget_.reset();
87
88 ViewsTestBase::TearDown();
89 }
90
91 protected:
slide_out_controller()92 SlideOutController* slide_out_controller() {
93 return slide_out_controller_.get();
94 }
95
delegate()96 TestSlideOutControllerDelegate* delegate() { return delegate_.get(); }
97
PostSequentialGestureEvent(const ui::GestureEventDetails & details)98 void PostSequentialGestureEvent(const ui::GestureEventDetails& details) {
99 // Set the timestamp ahead one microsecond.
100 sequential_event_timestamp_ += base::TimeDelta::FromMicroseconds(1);
101
102 ui::GestureEvent gesture_event(
103 0, 0, ui::EF_NONE, base::TimeTicks() + sequential_event_timestamp_,
104 details);
105 slide_out_controller()->OnGestureEvent(&gesture_event);
106 }
107
PostSequentialSwipeEvent(int swipe_amount)108 void PostSequentialSwipeEvent(int swipe_amount) {
109 PostSequentialGestureEvent(
110 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
111 PostSequentialGestureEvent(
112 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, swipe_amount, 0));
113 PostSequentialGestureEvent(
114 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
115 }
116
117 private:
118 std::unique_ptr<Widget> widget_;
119 std::unique_ptr<SlideOutController> slide_out_controller_;
120 std::unique_ptr<TestSlideOutControllerDelegate> delegate_;
121 base::TimeDelta sequential_event_timestamp_;
122 };
123
TEST_F(SlideOutControllerTest,OnGestureEventAndDelegate)124 TEST_F(SlideOutControllerTest, OnGestureEventAndDelegate) {
125 PostSequentialGestureEvent(
126 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
127
128 EXPECT_EQ(1, delegate()->slide_started_count_);
129 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
130 EXPECT_EQ(0, delegate()->slide_out_count_);
131
132 delegate()->reset();
133
134 PostSequentialGestureEvent(
135 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE));
136
137 EXPECT_EQ(0, delegate()->slide_started_count_);
138 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
139 EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
140 EXPECT_EQ(0, delegate()->slide_out_count_);
141
142 delegate()->reset();
143
144 PostSequentialGestureEvent(
145 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
146
147 EXPECT_EQ(0, delegate()->slide_started_count_);
148 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
149 EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
150 EXPECT_EQ(0, delegate()->slide_out_count_);
151 }
152
TEST_F(SlideOutControllerTest,SlideOutAndClose)153 TEST_F(SlideOutControllerTest, SlideOutAndClose) {
154 // Place a finger on notification.
155 PostSequentialGestureEvent(
156 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
157
158 EXPECT_EQ(1, delegate()->slide_started_count_);
159 EXPECT_EQ(0, delegate()->slide_changed_count_);
160 EXPECT_EQ(0, delegate()->slide_out_count_);
161
162 delegate()->reset();
163
164 // Move the finger horizontally by 101 px. (101 px is more than half of the
165 // target width 200 px)
166 PostSequentialGestureEvent(ui::GestureEventDetails(
167 ui::ET_GESTURE_SCROLL_UPDATE, kTargetWidth / 2 + 1, 0));
168
169 EXPECT_EQ(0, delegate()->slide_started_count_);
170 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
171 EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
172 EXPECT_EQ(0, delegate()->slide_out_count_);
173
174 delegate()->reset();
175
176 // Release the finger.
177 PostSequentialGestureEvent(
178 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
179
180 EXPECT_EQ(0, delegate()->slide_started_count_);
181 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
182 EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
183 EXPECT_EQ(0, delegate()->slide_out_count_);
184 // The target has been scrolled out and the current location is moved by the
185 // width (200px).
186 EXPECT_EQ(kTargetWidth,
187 delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());
188
189 delegate()->reset();
190
191 // Ensure a deferred OnSlideOut handler is called.
192 base::RunLoop().RunUntilIdle();
193 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
194 EXPECT_EQ(1, delegate()->slide_out_count_);
195 }
196
TEST_F(SlideOutControllerTest,SlideLittleAmountAndNotClose)197 TEST_F(SlideOutControllerTest, SlideLittleAmountAndNotClose) {
198 // Place a finger on notification.
199 PostSequentialGestureEvent(
200 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
201
202 EXPECT_EQ(1, delegate()->slide_started_count_);
203 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
204 EXPECT_EQ(0, delegate()->slide_out_count_);
205
206 delegate()->reset();
207
208 // Move the finger horizontally by 99 px. (99 px is less than half of the
209 // target width 200 px)
210 PostSequentialGestureEvent(
211 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 99, 0));
212
213 EXPECT_EQ(0, delegate()->slide_started_count_);
214 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
215 EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
216 EXPECT_EQ(0, delegate()->slide_out_count_);
217
218 delegate()->reset();
219
220 // Release the finger.
221 PostSequentialGestureEvent(
222 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
223
224 EXPECT_EQ(0, delegate()->slide_started_count_);
225 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
226 EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
227 EXPECT_EQ(0, delegate()->slide_out_count_);
228 // The target has been moved back to the origin.
229 EXPECT_EQ(0.f,
230 delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());
231
232 delegate()->reset();
233
234 // Ensure no deferred SlideOut handler.
235 base::RunLoop().RunUntilIdle();
236 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
237 EXPECT_EQ(0, delegate()->slide_out_count_);
238 }
239
TEST_F(SlideOutControllerTest,SetSwipeControlWidth_SwipeLessThanControlWidth)240 TEST_F(SlideOutControllerTest, SetSwipeControlWidth_SwipeLessThanControlWidth) {
241 // Set the width of swipe control.
242 slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);
243
244 // Place a finger on notification.
245 PostSequentialGestureEvent(
246 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
247
248 EXPECT_EQ(1, delegate()->slide_started_count_);
249 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
250 EXPECT_EQ(0, delegate()->slide_out_count_);
251
252 delegate()->reset();
253
254 // Move the finger horizontally by 29 px. (29 px is less than the swipe
255 // control width).
256 PostSequentialGestureEvent(ui::GestureEventDetails(
257 ui::ET_GESTURE_SCROLL_UPDATE, kSwipeControlWidth - 1, 0));
258
259 EXPECT_EQ(0, delegate()->slide_started_count_);
260 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
261 EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
262 EXPECT_EQ(0, delegate()->slide_out_count_);
263
264 delegate()->reset();
265
266 // Release the finger.
267 PostSequentialGestureEvent(
268 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
269
270 EXPECT_EQ(0, delegate()->slide_started_count_);
271 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
272 EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
273 EXPECT_EQ(0, delegate()->slide_out_count_);
274 // The target has been moved back to the origin.
275 EXPECT_EQ(0.f,
276 delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());
277
278 delegate()->reset();
279
280 // Ensure no deferred SlideOut handler.
281 base::RunLoop().RunUntilIdle();
282 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
283 EXPECT_EQ(0, delegate()->slide_out_count_);
284 }
285
TEST_F(SlideOutControllerTest,SwipeControlWidth_SwipeMoreThanControlWidth)286 TEST_F(SlideOutControllerTest, SwipeControlWidth_SwipeMoreThanControlWidth) {
287 // Set the width of swipe control.
288 slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);
289
290 // Place a finger on notification.
291 PostSequentialGestureEvent(
292 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
293
294 EXPECT_EQ(1, delegate()->slide_started_count_);
295 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
296 EXPECT_EQ(0, delegate()->slide_out_count_);
297
298 delegate()->reset();
299
300 // Move the finger horizontally by 31 px. (31 px is more than the swipe
301 // control width).
302 PostSequentialGestureEvent(ui::GestureEventDetails(
303 ui::ET_GESTURE_SCROLL_UPDATE, kSwipeControlWidth + 1, 0));
304
305 EXPECT_EQ(0, delegate()->slide_started_count_);
306 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
307 EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
308 EXPECT_EQ(0, delegate()->slide_out_count_);
309
310 delegate()->reset();
311
312 // Release the finger.
313 PostSequentialGestureEvent(
314 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
315
316 EXPECT_EQ(0, delegate()->slide_started_count_);
317 // Slide is in progress.
318 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
319 EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
320 EXPECT_EQ(0, delegate()->slide_out_count_);
321 // Swipe amount is the swipe control width.
322 EXPECT_EQ(kSwipeControlWidth,
323 delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());
324
325 delegate()->reset();
326
327 // Ensure no deferred SlideOut handler.
328 base::RunLoop().RunUntilIdle();
329 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
330 EXPECT_EQ(0, delegate()->slide_out_count_);
331 }
332
TEST_F(SlideOutControllerTest,SetSwipeControlWidth_SwipeOut)333 TEST_F(SlideOutControllerTest, SetSwipeControlWidth_SwipeOut) {
334 // Set the width of swipe control.
335 slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);
336
337 // Place a finger on notification.
338 PostSequentialGestureEvent(
339 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
340
341 EXPECT_EQ(1, delegate()->slide_started_count_);
342 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
343 EXPECT_EQ(0, delegate()->slide_out_count_);
344
345 delegate()->reset();
346
347 // Move the finger horizontally by 101 px. (101 px is more than the half of
348 // the target width).
349 PostSequentialGestureEvent(ui::GestureEventDetails(
350 ui::ET_GESTURE_SCROLL_UPDATE, kTargetWidth / 2 + 1, 0));
351
352 EXPECT_EQ(0, delegate()->slide_started_count_);
353 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
354 EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
355 EXPECT_EQ(0, delegate()->slide_out_count_);
356
357 delegate()->reset();
358
359 // Release the finger.
360 PostSequentialGestureEvent(
361 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
362
363 // ... and it is automatically slided out.
364 EXPECT_EQ(0, delegate()->slide_started_count_);
365 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
366 EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
367 EXPECT_EQ(0, delegate()->slide_out_count_);
368 EXPECT_EQ(kTargetWidth,
369 delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());
370
371 delegate()->reset();
372
373 // Ensure a deferred SlideOut handler is called once.
374 base::RunLoop().RunUntilIdle();
375 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
376 EXPECT_EQ(1, delegate()->slide_out_count_);
377 }
378
TEST_F(SlideOutControllerTest,SwipeControlWidth_SnapAndSwipeOut)379 TEST_F(SlideOutControllerTest, SwipeControlWidth_SnapAndSwipeOut) {
380 // Set the width of swipe control.
381 slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);
382
383 // Snap to the swipe control.
384 PostSequentialGestureEvent(
385 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
386 PostSequentialGestureEvent(ui::GestureEventDetails(
387 ui::ET_GESTURE_SCROLL_UPDATE, kSwipeControlWidth, 0));
388 PostSequentialGestureEvent(
389 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
390 EXPECT_EQ(1, delegate()->slide_started_count_);
391 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
392 EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
393 EXPECT_EQ(0, delegate()->slide_out_count_);
394 EXPECT_EQ(kSwipeControlWidth,
395 delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());
396
397 delegate()->reset();
398
399 // Swipe horizontally by 70 px.
400 PostSequentialGestureEvent(
401 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
402 PostSequentialGestureEvent(
403 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 70, 0));
404 PostSequentialGestureEvent(
405 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
406
407 // ... and it is automatically slided out.
408 EXPECT_EQ(1, delegate()->slide_started_count_);
409 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
410 EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
411 EXPECT_EQ(0, delegate()->slide_out_count_);
412 EXPECT_EQ(kTargetWidth,
413 delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());
414
415 delegate()->reset();
416
417 // Ensure a deferred OnSlideOut handler is called.
418 base::RunLoop().RunUntilIdle();
419 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
420 EXPECT_EQ(1, delegate()->slide_out_count_);
421 }
422
TEST_F(SlideOutControllerTest,SwipeControlWidth_SnapAndSnapToControl)423 TEST_F(SlideOutControllerTest, SwipeControlWidth_SnapAndSnapToControl) {
424 // Set the width of swipe control.
425 slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);
426
427 // Snap to the swipe control.
428 PostSequentialSwipeEvent(kSwipeControlWidth + 10);
429 EXPECT_EQ(1, delegate()->slide_started_count_);
430 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
431 EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
432 EXPECT_EQ(0, delegate()->slide_out_count_);
433 EXPECT_EQ(kSwipeControlWidth,
434 delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());
435
436 delegate()->reset();
437
438 // Swipe horizontally by 40 px for the same direction.
439 PostSequentialSwipeEvent(40);
440
441 // Snap automatically back to the swipe control.
442 EXPECT_EQ(1, delegate()->slide_started_count_);
443 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
444 EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
445 EXPECT_EQ(0, delegate()->slide_out_count_);
446 EXPECT_EQ(kSwipeControlWidth,
447 delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());
448
449 delegate()->reset();
450
451 // Ensure no deferred OnSlideOut handler.
452 base::RunLoop().RunUntilIdle();
453 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
454 EXPECT_EQ(0, delegate()->slide_out_count_);
455 }
456
TEST_F(SlideOutControllerTest,SwipeControlWidth_SnapAndBackToOrigin)457 TEST_F(SlideOutControllerTest, SwipeControlWidth_SnapAndBackToOrigin) {
458 // Set the width of swipe control.
459 slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);
460
461 // Snap to the swipe control.
462 PostSequentialSwipeEvent(kSwipeControlWidth + 20);
463 EXPECT_EQ(1, delegate()->slide_started_count_);
464 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
465 EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
466 EXPECT_EQ(0, delegate()->slide_out_count_);
467 EXPECT_EQ(kSwipeControlWidth,
468 delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());
469
470 delegate()->reset();
471
472 // Swipe to the reversed direction by -1 px.
473 PostSequentialSwipeEvent(-1);
474
475 // Snap automatically back to the origin.
476 EXPECT_EQ(1, delegate()->slide_started_count_);
477 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
478 EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
479 EXPECT_EQ(0, delegate()->slide_out_count_);
480 EXPECT_EQ(0,
481 delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());
482
483 delegate()->reset();
484
485 // Ensure no deferred OnSlideOut handler.
486 base::RunLoop().RunUntilIdle();
487 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
488 EXPECT_EQ(0, delegate()->slide_out_count_);
489 }
490
TEST_F(SlideOutControllerTest,SwipeControlWidth_NotSnapAndBackToOrigin)491 TEST_F(SlideOutControllerTest, SwipeControlWidth_NotSnapAndBackToOrigin) {
492 // Set the width of swipe control.
493 slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);
494
495 // Swipe partially but it's not enough to snap to the swipe control. So it is
496 // back to the origin
497 PostSequentialSwipeEvent(kSwipeControlWidth - 1);
498 EXPECT_EQ(1, delegate()->slide_started_count_);
499 EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
500 EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
501 EXPECT_EQ(0, delegate()->slide_out_count_);
502 EXPECT_EQ(0,
503 delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());
504
505 delegate()->reset();
506
507 // Ensure no deferred OnSlideOut handler.
508 base::RunLoop().RunUntilIdle();
509 EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
510 EXPECT_EQ(0, delegate()->slide_out_count_);
511 }
512
513 } // namespace views
514