1 // Copyright 2014 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 "ui/touch_selection/touch_handle.h"
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "ui/events/test/motion_event_test_utils.h"
9 #include "ui/gfx/geometry/rect_f.h"
10 #include "ui/touch_selection/touch_handle_orientation.h"
12 using ui::test::MockMotionEvent
;
17 const int kDefaultTapDurationMs
= 200;
18 const double kDefaultTapSlop
= 10.;
19 const float kDefaultDrawableSize
= 10.f
;
21 struct MockDrawableData
{
23 : orientation(TouchHandleOrientation::UNDEFINED
),
27 rect(0, 0, kDefaultDrawableSize
, kDefaultDrawableSize
) {}
28 TouchHandleOrientation orientation
;
35 class MockTouchHandleDrawable
: public TouchHandleDrawable
{
37 explicit MockTouchHandleDrawable(MockDrawableData
* data
) : data_(data
) {}
38 ~MockTouchHandleDrawable() override
{}
40 void SetEnabled(bool enabled
) override
{ data_
->enabled
= enabled
; }
42 void SetOrientation(TouchHandleOrientation orientation
) override
{
43 data_
->orientation
= orientation
;
46 void SetAlpha(float alpha
) override
{
48 data_
->visible
= alpha
> 0;
51 void SetFocus(const gfx::PointF
& position
) override
{
52 // Anchor focus to the top left of the rect (regardless of orientation).
53 data_
->rect
.set_origin(position
);
56 gfx::RectF
GetVisibleBounds() const override
{
61 MockDrawableData
* data_
;
66 class TouchHandleTest
: public testing::Test
, public TouchHandleClient
{
72 needs_animate_(false) {}
74 ~TouchHandleTest() override
{}
76 // TouchHandleClient implementation.
77 void OnDragBegin(const TouchSelectionDraggable
& handler
,
78 const gfx::PointF
& drag_position
) override
{
82 void OnDragUpdate(const TouchSelectionDraggable
& handler
,
83 const gfx::PointF
& drag_position
) override
{
85 drag_position_
= drag_position
;
88 void OnDragEnd(const TouchSelectionDraggable
& handler
) override
{
92 bool IsWithinTapSlop(const gfx::Vector2dF
& delta
) const override
{
93 return delta
.LengthSquared() < (kDefaultTapSlop
* kDefaultTapSlop
);
96 void OnHandleTapped(const TouchHandle
& handle
) override
{ tapped_
= true; }
98 void SetNeedsAnimate() override
{ needs_animate_
= true; }
100 scoped_ptr
<TouchHandleDrawable
> CreateDrawable() override
{
101 return make_scoped_ptr(new MockTouchHandleDrawable(&drawable_data_
));
104 base::TimeDelta
GetMaxTapDuration() const override
{
105 return base::TimeDelta::FromMilliseconds(kDefaultTapDurationMs
);
108 void Animate(TouchHandle
& handle
) {
109 needs_animate_
= false;
110 base::TimeTicks now
= base::TimeTicks::Now();
111 while (handle
.Animate(now
))
112 now
+= base::TimeDelta::FromMilliseconds(16);
115 bool GetAndResetHandleDragged() {
116 bool dragged
= dragged_
;
121 bool GetAndResetHandleTapped() {
122 bool tapped
= tapped_
;
127 bool GetAndResetNeedsAnimate() {
128 bool needs_animate
= needs_animate_
;
129 needs_animate_
= false;
130 return needs_animate
;
133 bool IsDragging() const { return dragging_
; }
134 const gfx::PointF
& DragPosition() const { return drag_position_
; }
135 bool NeedsAnimate() const { return needs_animate_
; }
137 const MockDrawableData
& drawable() { return drawable_data_
; }
140 gfx::PointF drag_position_
;
146 MockDrawableData drawable_data_
;
149 TEST_F(TouchHandleTest
, Visibility
) {
150 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
151 EXPECT_FALSE(drawable().visible
);
153 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
154 EXPECT_TRUE(drawable().visible
);
155 EXPECT_EQ(1.f
, drawable().alpha
);
157 handle
.SetVisible(false, TouchHandle::ANIMATION_NONE
);
158 EXPECT_FALSE(drawable().visible
);
160 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
161 EXPECT_TRUE(drawable().visible
);
162 EXPECT_EQ(1.f
, drawable().alpha
);
165 TEST_F(TouchHandleTest
, VisibilityAnimation
) {
166 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
167 ASSERT_FALSE(NeedsAnimate());
168 ASSERT_FALSE(drawable().visible
);
169 ASSERT_EQ(0.f
, drawable().alpha
);
171 handle
.SetVisible(true, TouchHandle::ANIMATION_SMOOTH
);
172 EXPECT_TRUE(NeedsAnimate());
173 EXPECT_FALSE(drawable().visible
);
174 EXPECT_EQ(0.f
, drawable().alpha
);
177 EXPECT_TRUE(drawable().visible
);
178 EXPECT_EQ(1.f
, drawable().alpha
);
180 ASSERT_FALSE(NeedsAnimate());
181 handle
.SetVisible(false, TouchHandle::ANIMATION_SMOOTH
);
182 EXPECT_TRUE(NeedsAnimate());
183 EXPECT_TRUE(drawable().visible
);
184 EXPECT_EQ(1.f
, drawable().alpha
);
187 EXPECT_FALSE(drawable().visible
);
188 EXPECT_EQ(0.f
, drawable().alpha
);
190 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
191 EXPECT_EQ(1.f
, drawable().alpha
);
192 EXPECT_FALSE(GetAndResetNeedsAnimate());
193 handle
.SetVisible(false, TouchHandle::ANIMATION_SMOOTH
);
194 EXPECT_EQ(1.f
, drawable().alpha
);
195 EXPECT_TRUE(GetAndResetNeedsAnimate());
196 handle
.SetVisible(true, TouchHandle::ANIMATION_SMOOTH
);
197 EXPECT_EQ(1.f
, drawable().alpha
);
198 EXPECT_FALSE(GetAndResetNeedsAnimate());
201 TEST_F(TouchHandleTest
, Orientation
) {
202 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
203 EXPECT_EQ(TouchHandleOrientation::CENTER
, drawable().orientation
);
205 handle
.SetOrientation(TouchHandleOrientation::LEFT
);
206 EXPECT_EQ(TouchHandleOrientation::LEFT
, drawable().orientation
);
208 handle
.SetOrientation(TouchHandleOrientation::RIGHT
);
209 EXPECT_EQ(TouchHandleOrientation::RIGHT
, drawable().orientation
);
211 handle
.SetOrientation(TouchHandleOrientation::CENTER
);
212 EXPECT_EQ(TouchHandleOrientation::CENTER
, drawable().orientation
);
215 TEST_F(TouchHandleTest
, Position
) {
216 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
217 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
219 gfx::PointF position
;
220 EXPECT_EQ(gfx::PointF(), drawable().rect
.origin());
222 position
= gfx::PointF(7.3f
, -3.7f
);
223 handle
.SetPosition(position
);
224 EXPECT_EQ(position
, drawable().rect
.origin());
226 position
= gfx::PointF(-7.3f
, 3.7f
);
227 handle
.SetPosition(position
);
228 EXPECT_EQ(position
, drawable().rect
.origin());
231 TEST_F(TouchHandleTest
, PositionNotUpdatedWhileFadingOrInvisible
) {
232 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
234 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
235 ASSERT_TRUE(drawable().visible
);
236 ASSERT_FALSE(NeedsAnimate());
238 gfx::PointF
old_position(7.3f
, -3.7f
);
239 handle
.SetPosition(old_position
);
240 ASSERT_EQ(old_position
, drawable().rect
.origin());
242 handle
.SetVisible(false, TouchHandle::ANIMATION_SMOOTH
);
243 ASSERT_TRUE(NeedsAnimate());
245 gfx::PointF
new_position(3.7f
, -3.7f
);
246 handle
.SetPosition(new_position
);
247 EXPECT_EQ(old_position
, drawable().rect
.origin());
248 EXPECT_TRUE(NeedsAnimate());
250 // While the handle is fading, the new position should not take affect.
251 base::TimeTicks now
= base::TimeTicks::Now();
252 while (handle
.Animate(now
)) {
253 EXPECT_EQ(old_position
, drawable().rect
.origin());
254 now
+= base::TimeDelta::FromMilliseconds(16);
257 // Even after the animation terminates, the new position will not be pushed.
258 EXPECT_EQ(old_position
, drawable().rect
.origin());
260 // As soon as the handle becomes visible, the new position will be pushed.
261 handle
.SetVisible(true, TouchHandle::ANIMATION_SMOOTH
);
262 EXPECT_EQ(new_position
, drawable().rect
.origin());
265 TEST_F(TouchHandleTest
, Enabled
) {
266 // A newly created handle defaults to enabled.
267 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
268 EXPECT_TRUE(drawable().enabled
);
270 handle
.SetVisible(true, TouchHandle::ANIMATION_SMOOTH
);
271 EXPECT_TRUE(GetAndResetNeedsAnimate());
272 EXPECT_EQ(0.f
, drawable().alpha
);
273 handle
.SetEnabled(false);
274 EXPECT_FALSE(drawable().enabled
);
276 // Dragging should not be allowed while the handle is disabled.
277 base::TimeTicks event_time
= base::TimeTicks::Now();
278 const float kOffset
= kDefaultDrawableSize
/ 2.f
;
279 MockMotionEvent
event(
280 MockMotionEvent::ACTION_DOWN
, event_time
, kOffset
, kOffset
);
281 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
283 // Disabling mid-animation should cancel the animation.
284 handle
.SetEnabled(true);
285 handle
.SetVisible(false, TouchHandle::ANIMATION_SMOOTH
);
286 EXPECT_TRUE(drawable().visible
);
287 EXPECT_TRUE(GetAndResetNeedsAnimate());
288 handle
.SetEnabled(false);
289 EXPECT_FALSE(drawable().enabled
);
290 EXPECT_FALSE(drawable().visible
);
291 EXPECT_FALSE(handle
.Animate(base::TimeTicks::Now()));
293 // Disabling mid-drag should cancel the drag.
294 handle
.SetEnabled(true);
295 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
296 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
297 EXPECT_TRUE(IsDragging());
298 handle
.SetEnabled(false);
299 EXPECT_FALSE(IsDragging());
300 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
303 TEST_F(TouchHandleTest
, Drag
) {
304 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
306 base::TimeTicks event_time
= base::TimeTicks::Now();
307 const float kOffset
= kDefaultDrawableSize
/ 2.f
;
309 // The handle must be visible to trigger drag.
310 MockMotionEvent
event(
311 MockMotionEvent::ACTION_DOWN
, event_time
, kOffset
, kOffset
);
312 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
313 EXPECT_FALSE(IsDragging());
314 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
316 // ACTION_DOWN must fall within the drawable region to trigger drag.
317 event
= MockMotionEvent(MockMotionEvent::ACTION_DOWN
, event_time
, 50, 50);
318 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
319 EXPECT_FALSE(IsDragging());
321 // Only ACTION_DOWN will trigger drag.
322 event
= MockMotionEvent(
323 MockMotionEvent::ACTION_MOVE
, event_time
, kOffset
, kOffset
);
324 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
325 EXPECT_FALSE(IsDragging());
328 event
= MockMotionEvent(
329 MockMotionEvent::ACTION_DOWN
, event_time
, kOffset
, kOffset
);
330 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
331 EXPECT_TRUE(IsDragging());
333 event
= MockMotionEvent(
334 MockMotionEvent::ACTION_MOVE
, event_time
, kOffset
+ 10, kOffset
+ 15);
335 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
336 EXPECT_TRUE(GetAndResetHandleDragged());
337 EXPECT_TRUE(IsDragging());
338 EXPECT_EQ(gfx::PointF(10, 15), DragPosition());
340 event
= MockMotionEvent(
341 MockMotionEvent::ACTION_MOVE
, event_time
, kOffset
- 10, kOffset
- 15);
342 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
343 EXPECT_TRUE(GetAndResetHandleDragged());
344 EXPECT_TRUE(IsDragging());
345 EXPECT_EQ(gfx::PointF(-10, -15), DragPosition());
347 event
= MockMotionEvent(MockMotionEvent::ACTION_UP
);
348 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
349 EXPECT_FALSE(GetAndResetHandleDragged());
350 EXPECT_FALSE(IsDragging());
352 // Non-ACTION_DOWN events after the drag has terminated should not be handled.
353 event
= MockMotionEvent(MockMotionEvent::ACTION_CANCEL
);
354 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
357 TEST_F(TouchHandleTest
, DragDefersOrientationChange
) {
358 TouchHandle
handle(this, TouchHandleOrientation::RIGHT
);
359 ASSERT_EQ(drawable().orientation
, TouchHandleOrientation::RIGHT
);
360 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
362 MockMotionEvent
event(MockMotionEvent::ACTION_DOWN
);
363 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
364 EXPECT_TRUE(IsDragging());
366 // Orientation changes will be deferred until the drag ends.
367 handle
.SetOrientation(TouchHandleOrientation::LEFT
);
368 EXPECT_EQ(TouchHandleOrientation::RIGHT
, drawable().orientation
);
370 event
= MockMotionEvent(MockMotionEvent::ACTION_MOVE
);
371 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
372 EXPECT_TRUE(GetAndResetHandleDragged());
373 EXPECT_TRUE(IsDragging());
374 EXPECT_EQ(TouchHandleOrientation::RIGHT
, drawable().orientation
);
376 event
= MockMotionEvent(MockMotionEvent::ACTION_UP
);
377 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
378 EXPECT_FALSE(GetAndResetHandleDragged());
379 EXPECT_FALSE(IsDragging());
380 EXPECT_EQ(TouchHandleOrientation::LEFT
, drawable().orientation
);
383 TEST_F(TouchHandleTest
, DragDefersFade
) {
384 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
385 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
387 MockMotionEvent
event(MockMotionEvent::ACTION_DOWN
);
388 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
389 EXPECT_TRUE(IsDragging());
391 // Fade will be deferred until the drag ends.
392 handle
.SetVisible(false, TouchHandle::ANIMATION_SMOOTH
);
393 EXPECT_FALSE(NeedsAnimate());
394 EXPECT_TRUE(drawable().visible
);
395 EXPECT_EQ(1.f
, drawable().alpha
);
397 event
= MockMotionEvent(MockMotionEvent::ACTION_MOVE
);
398 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
399 EXPECT_FALSE(NeedsAnimate());
400 EXPECT_TRUE(drawable().visible
);
402 event
= MockMotionEvent(MockMotionEvent::ACTION_UP
);
403 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
404 EXPECT_FALSE(IsDragging());
405 EXPECT_TRUE(NeedsAnimate());
408 EXPECT_FALSE(drawable().visible
);
409 EXPECT_EQ(0.f
, drawable().alpha
);
412 TEST_F(TouchHandleTest
, DragTargettingUsesTouchSize
) {
413 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
414 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
416 base::TimeTicks event_time
= base::TimeTicks::Now();
417 const float kTouchSize
= 24.f
;
418 const float kOffset
= kDefaultDrawableSize
+ kTouchSize
/ 2.001f
;
420 MockMotionEvent
event(
421 MockMotionEvent::ACTION_DOWN
, event_time
, kOffset
, 0);
422 event
.SetTouchMajor(0.f
);
423 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
424 EXPECT_FALSE(IsDragging());
426 event
.SetTouchMajor(kTouchSize
/ 2.f
);
427 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
428 EXPECT_FALSE(IsDragging());
430 event
.SetTouchMajor(kTouchSize
);
431 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
432 EXPECT_TRUE(IsDragging());
434 event
.SetTouchMajor(kTouchSize
* 2.f
);
435 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
436 EXPECT_TRUE(IsDragging());
438 // The touch hit test region should be circular.
439 event
= MockMotionEvent(
440 MockMotionEvent::ACTION_DOWN
, event_time
, kOffset
, kOffset
);
441 event
.SetTouchMajor(kTouchSize
);
442 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
443 EXPECT_FALSE(IsDragging());
445 event
.SetTouchMajor(kTouchSize
* std::sqrt(2.f
) - 0.1f
);
446 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
447 EXPECT_FALSE(IsDragging());
449 event
.SetTouchMajor(kTouchSize
* std::sqrt(2.f
) + 0.1f
);
450 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
451 EXPECT_TRUE(IsDragging());
453 // Ensure a touch size of 0 can still register a hit.
454 event
= MockMotionEvent(MockMotionEvent::ACTION_DOWN
,
456 kDefaultDrawableSize
/ 2.f
,
457 kDefaultDrawableSize
/ 2.f
);
458 event
.SetTouchMajor(0);
459 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
460 EXPECT_TRUE(IsDragging());
462 // Touches centered above the handle region should never register a hit, even
463 // if the touch region intersects the handle region.
464 event
= MockMotionEvent(MockMotionEvent::ACTION_DOWN
, event_time
,
465 kDefaultDrawableSize
/ 2.f
, -kTouchSize
/ 3.f
);
466 event
.SetTouchMajor(kTouchSize
);
467 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
468 EXPECT_FALSE(IsDragging());
471 TEST_F(TouchHandleTest
, Tap
) {
472 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
473 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
475 base::TimeTicks event_time
= base::TimeTicks::Now();
477 // ACTION_CANCEL shouldn't trigger a tap.
478 MockMotionEvent
event(MockMotionEvent::ACTION_DOWN
, event_time
, 0, 0);
479 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
480 event_time
+= base::TimeDelta::FromMilliseconds(50);
481 event
= MockMotionEvent(MockMotionEvent::ACTION_CANCEL
, event_time
, 0, 0);
482 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
483 EXPECT_FALSE(GetAndResetHandleTapped());
485 // Long press shouldn't trigger a tap.
486 event
= MockMotionEvent(MockMotionEvent::ACTION_DOWN
, event_time
, 0, 0);
487 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
488 event_time
+= 2 * GetMaxTapDuration();
489 event
= MockMotionEvent(MockMotionEvent::ACTION_UP
, event_time
, 0, 0);
490 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
491 EXPECT_FALSE(GetAndResetHandleTapped());
493 // Only a brief tap within the slop region should trigger a tap.
494 event
= MockMotionEvent(MockMotionEvent::ACTION_DOWN
, event_time
, 0, 0);
495 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
496 event_time
+= GetMaxTapDuration() / 2;
497 event
= MockMotionEvent(
498 MockMotionEvent::ACTION_MOVE
, event_time
, kDefaultTapSlop
/ 2.f
, 0);
499 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
500 event
= MockMotionEvent(
501 MockMotionEvent::ACTION_UP
, event_time
, kDefaultTapSlop
/ 2.f
, 0);
502 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
503 EXPECT_TRUE(GetAndResetHandleTapped());
505 // Moving beyond the slop region shouldn't trigger a tap.
506 event
= MockMotionEvent(MockMotionEvent::ACTION_DOWN
, event_time
, 0, 0);
507 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
508 event_time
+= GetMaxTapDuration() / 2;
509 event
= MockMotionEvent(
510 MockMotionEvent::ACTION_MOVE
, event_time
, kDefaultTapSlop
* 2.f
, 0);
511 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
512 event
= MockMotionEvent(
513 MockMotionEvent::ACTION_UP
, event_time
, kDefaultTapSlop
* 2.f
, 0);
514 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
515 EXPECT_FALSE(GetAndResetHandleTapped());