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 kDefaultTapTimeoutMs
= 200;
18 const float kDefaultTapSlop
= 10.f
;
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 OnHandleDragBegin(const TouchHandle
& handle
) override
{
81 void OnHandleDragUpdate(const TouchHandle
& handle
,
82 const gfx::PointF
& new_position
) override
{
84 drag_position_
= new_position
;
87 void OnHandleDragEnd(const TouchHandle
& handle
) override
{
91 void OnHandleTapped(const TouchHandle
& handle
) override
{ tapped_
= true; }
93 void SetNeedsAnimate() override
{ needs_animate_
= true; }
95 scoped_ptr
<TouchHandleDrawable
> CreateDrawable() override
{
96 return make_scoped_ptr(new MockTouchHandleDrawable(&drawable_data_
));
99 base::TimeDelta
GetTapTimeout() const override
{
100 return base::TimeDelta::FromMilliseconds(kDefaultTapTimeoutMs
);
103 float GetTapSlop() const override
{ return kDefaultTapSlop
; }
105 void Animate(TouchHandle
& handle
) {
106 needs_animate_
= false;
107 base::TimeTicks now
= base::TimeTicks::Now();
108 while (handle
.Animate(now
))
109 now
+= base::TimeDelta::FromMilliseconds(16);
112 bool GetAndResetHandleDragged() {
113 bool dragged
= dragged_
;
118 bool GetAndResetHandleTapped() {
119 bool tapped
= tapped_
;
124 bool GetAndResetNeedsAnimate() {
125 bool needs_animate
= needs_animate_
;
126 needs_animate_
= false;
127 return needs_animate
;
130 bool IsDragging() const { return dragging_
; }
131 const gfx::PointF
& DragPosition() const { return drag_position_
; }
132 bool NeedsAnimate() const { return needs_animate_
; }
134 const MockDrawableData
& drawable() { return drawable_data_
; }
137 gfx::PointF drag_position_
;
143 MockDrawableData drawable_data_
;
146 TEST_F(TouchHandleTest
, Visibility
) {
147 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
148 EXPECT_FALSE(drawable().visible
);
150 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
151 EXPECT_TRUE(drawable().visible
);
152 EXPECT_EQ(1.f
, drawable().alpha
);
154 handle
.SetVisible(false, TouchHandle::ANIMATION_NONE
);
155 EXPECT_FALSE(drawable().visible
);
157 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
158 EXPECT_TRUE(drawable().visible
);
159 EXPECT_EQ(1.f
, drawable().alpha
);
162 TEST_F(TouchHandleTest
, VisibilityAnimation
) {
163 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
164 ASSERT_FALSE(NeedsAnimate());
165 ASSERT_FALSE(drawable().visible
);
166 ASSERT_EQ(0.f
, drawable().alpha
);
168 handle
.SetVisible(true, TouchHandle::ANIMATION_SMOOTH
);
169 EXPECT_TRUE(NeedsAnimate());
170 EXPECT_FALSE(drawable().visible
);
171 EXPECT_EQ(0.f
, drawable().alpha
);
174 EXPECT_TRUE(drawable().visible
);
175 EXPECT_EQ(1.f
, drawable().alpha
);
177 ASSERT_FALSE(NeedsAnimate());
178 handle
.SetVisible(false, TouchHandle::ANIMATION_SMOOTH
);
179 EXPECT_TRUE(NeedsAnimate());
180 EXPECT_TRUE(drawable().visible
);
181 EXPECT_EQ(1.f
, drawable().alpha
);
184 EXPECT_FALSE(drawable().visible
);
185 EXPECT_EQ(0.f
, drawable().alpha
);
187 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
188 EXPECT_EQ(1.f
, drawable().alpha
);
189 EXPECT_FALSE(GetAndResetNeedsAnimate());
190 handle
.SetVisible(false, TouchHandle::ANIMATION_SMOOTH
);
191 EXPECT_EQ(1.f
, drawable().alpha
);
192 EXPECT_TRUE(GetAndResetNeedsAnimate());
193 handle
.SetVisible(true, TouchHandle::ANIMATION_SMOOTH
);
194 EXPECT_EQ(1.f
, drawable().alpha
);
195 EXPECT_FALSE(GetAndResetNeedsAnimate());
198 TEST_F(TouchHandleTest
, Orientation
) {
199 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
200 EXPECT_EQ(TouchHandleOrientation::CENTER
, drawable().orientation
);
202 handle
.SetOrientation(TouchHandleOrientation::LEFT
);
203 EXPECT_EQ(TouchHandleOrientation::LEFT
, drawable().orientation
);
205 handle
.SetOrientation(TouchHandleOrientation::RIGHT
);
206 EXPECT_EQ(TouchHandleOrientation::RIGHT
, drawable().orientation
);
208 handle
.SetOrientation(TouchHandleOrientation::CENTER
);
209 EXPECT_EQ(TouchHandleOrientation::CENTER
, drawable().orientation
);
212 TEST_F(TouchHandleTest
, Position
) {
213 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
214 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
216 gfx::PointF position
;
217 EXPECT_EQ(gfx::PointF(), drawable().rect
.origin());
219 position
= gfx::PointF(7.3f
, -3.7f
);
220 handle
.SetPosition(position
);
221 EXPECT_EQ(position
, drawable().rect
.origin());
223 position
= gfx::PointF(-7.3f
, 3.7f
);
224 handle
.SetPosition(position
);
225 EXPECT_EQ(position
, drawable().rect
.origin());
228 TEST_F(TouchHandleTest
, PositionNotUpdatedWhileFadingOrInvisible
) {
229 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
231 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
232 ASSERT_TRUE(drawable().visible
);
233 ASSERT_FALSE(NeedsAnimate());
235 gfx::PointF
old_position(7.3f
, -3.7f
);
236 handle
.SetPosition(old_position
);
237 ASSERT_EQ(old_position
, drawable().rect
.origin());
239 handle
.SetVisible(false, TouchHandle::ANIMATION_SMOOTH
);
240 ASSERT_TRUE(NeedsAnimate());
242 gfx::PointF
new_position(3.7f
, -3.7f
);
243 handle
.SetPosition(new_position
);
244 EXPECT_EQ(old_position
, drawable().rect
.origin());
245 EXPECT_TRUE(NeedsAnimate());
247 // While the handle is fading, the new position should not take affect.
248 base::TimeTicks now
= base::TimeTicks::Now();
249 while (handle
.Animate(now
)) {
250 EXPECT_EQ(old_position
, drawable().rect
.origin());
251 now
+= base::TimeDelta::FromMilliseconds(16);
254 // Even after the animation terminates, the new position will not be pushed.
255 EXPECT_EQ(old_position
, drawable().rect
.origin());
257 // As soon as the handle becomes visible, the new position will be pushed.
258 handle
.SetVisible(true, TouchHandle::ANIMATION_SMOOTH
);
259 EXPECT_EQ(new_position
, drawable().rect
.origin());
262 TEST_F(TouchHandleTest
, Enabled
) {
263 // A newly created handle defaults to enabled.
264 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
265 EXPECT_TRUE(drawable().enabled
);
267 handle
.SetVisible(true, TouchHandle::ANIMATION_SMOOTH
);
268 EXPECT_TRUE(GetAndResetNeedsAnimate());
269 EXPECT_EQ(0.f
, drawable().alpha
);
270 handle
.SetEnabled(false);
271 EXPECT_FALSE(drawable().enabled
);
273 // Dragging should not be allowed while the handle is disabled.
274 base::TimeTicks event_time
= base::TimeTicks::Now();
275 const float kOffset
= kDefaultDrawableSize
/ 2.f
;
276 MockMotionEvent
event(
277 MockMotionEvent::ACTION_DOWN
, event_time
, kOffset
, kOffset
);
278 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
280 // Disabling mid-animation should cancel the animation.
281 handle
.SetEnabled(true);
282 handle
.SetVisible(false, TouchHandle::ANIMATION_SMOOTH
);
283 EXPECT_TRUE(drawable().visible
);
284 EXPECT_TRUE(GetAndResetNeedsAnimate());
285 handle
.SetEnabled(false);
286 EXPECT_FALSE(drawable().enabled
);
287 EXPECT_FALSE(drawable().visible
);
288 EXPECT_FALSE(handle
.Animate(base::TimeTicks::Now()));
290 // Disabling mid-drag should cancel the drag.
291 handle
.SetEnabled(true);
292 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
293 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
294 EXPECT_TRUE(IsDragging());
295 handle
.SetEnabled(false);
296 EXPECT_FALSE(IsDragging());
297 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
300 TEST_F(TouchHandleTest
, Drag
) {
301 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
303 base::TimeTicks event_time
= base::TimeTicks::Now();
304 const float kOffset
= kDefaultDrawableSize
/ 2.f
;
306 // The handle must be visible to trigger drag.
307 MockMotionEvent
event(
308 MockMotionEvent::ACTION_DOWN
, event_time
, kOffset
, kOffset
);
309 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
310 EXPECT_FALSE(IsDragging());
311 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
313 // ACTION_DOWN must fall within the drawable region to trigger drag.
314 event
= MockMotionEvent(MockMotionEvent::ACTION_DOWN
, event_time
, 50, 50);
315 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
316 EXPECT_FALSE(IsDragging());
318 // Only ACTION_DOWN will trigger drag.
319 event
= MockMotionEvent(
320 MockMotionEvent::ACTION_MOVE
, event_time
, kOffset
, kOffset
);
321 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
322 EXPECT_FALSE(IsDragging());
325 event
= MockMotionEvent(
326 MockMotionEvent::ACTION_DOWN
, event_time
, kOffset
, kOffset
);
327 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
328 EXPECT_TRUE(IsDragging());
330 event
= MockMotionEvent(
331 MockMotionEvent::ACTION_MOVE
, event_time
, kOffset
+ 10, kOffset
+ 15);
332 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
333 EXPECT_TRUE(GetAndResetHandleDragged());
334 EXPECT_TRUE(IsDragging());
335 EXPECT_EQ(gfx::PointF(10, 15), DragPosition());
337 event
= MockMotionEvent(
338 MockMotionEvent::ACTION_MOVE
, event_time
, kOffset
- 10, kOffset
- 15);
339 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
340 EXPECT_TRUE(GetAndResetHandleDragged());
341 EXPECT_TRUE(IsDragging());
342 EXPECT_EQ(gfx::PointF(-10, -15), DragPosition());
344 event
= MockMotionEvent(MockMotionEvent::ACTION_UP
);
345 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
346 EXPECT_FALSE(GetAndResetHandleDragged());
347 EXPECT_FALSE(IsDragging());
349 // Non-ACTION_DOWN events after the drag has terminated should not be handled.
350 event
= MockMotionEvent(MockMotionEvent::ACTION_CANCEL
);
351 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
354 TEST_F(TouchHandleTest
, DragDefersOrientationChange
) {
355 TouchHandle
handle(this, TouchHandleOrientation::RIGHT
);
356 ASSERT_EQ(drawable().orientation
, TouchHandleOrientation::RIGHT
);
357 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
359 MockMotionEvent
event(MockMotionEvent::ACTION_DOWN
);
360 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
361 EXPECT_TRUE(IsDragging());
363 // Orientation changes will be deferred until the drag ends.
364 handle
.SetOrientation(TouchHandleOrientation::LEFT
);
365 EXPECT_EQ(TouchHandleOrientation::RIGHT
, drawable().orientation
);
367 event
= MockMotionEvent(MockMotionEvent::ACTION_MOVE
);
368 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
369 EXPECT_TRUE(GetAndResetHandleDragged());
370 EXPECT_TRUE(IsDragging());
371 EXPECT_EQ(TouchHandleOrientation::RIGHT
, drawable().orientation
);
373 event
= MockMotionEvent(MockMotionEvent::ACTION_UP
);
374 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
375 EXPECT_FALSE(GetAndResetHandleDragged());
376 EXPECT_FALSE(IsDragging());
377 EXPECT_EQ(TouchHandleOrientation::LEFT
, drawable().orientation
);
380 TEST_F(TouchHandleTest
, DragDefersFade
) {
381 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
382 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
384 MockMotionEvent
event(MockMotionEvent::ACTION_DOWN
);
385 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
386 EXPECT_TRUE(IsDragging());
388 // Fade will be deferred until the drag ends.
389 handle
.SetVisible(false, TouchHandle::ANIMATION_SMOOTH
);
390 EXPECT_FALSE(NeedsAnimate());
391 EXPECT_TRUE(drawable().visible
);
392 EXPECT_EQ(1.f
, drawable().alpha
);
394 event
= MockMotionEvent(MockMotionEvent::ACTION_MOVE
);
395 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
396 EXPECT_FALSE(NeedsAnimate());
397 EXPECT_TRUE(drawable().visible
);
399 event
= MockMotionEvent(MockMotionEvent::ACTION_UP
);
400 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
401 EXPECT_FALSE(IsDragging());
402 EXPECT_TRUE(NeedsAnimate());
405 EXPECT_FALSE(drawable().visible
);
406 EXPECT_EQ(0.f
, drawable().alpha
);
409 TEST_F(TouchHandleTest
, DragTargettingUsesTouchSize
) {
410 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
411 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
413 base::TimeTicks event_time
= base::TimeTicks::Now();
414 const float kTouchSize
= 24.f
;
415 const float kOffset
= kDefaultDrawableSize
+ kTouchSize
/ 2.001f
;
417 MockMotionEvent
event(
418 MockMotionEvent::ACTION_DOWN
, event_time
, kOffset
, 0);
419 event
.SetTouchMajor(0.f
);
420 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
421 EXPECT_FALSE(IsDragging());
423 event
.SetTouchMajor(kTouchSize
/ 2.f
);
424 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
425 EXPECT_FALSE(IsDragging());
427 event
.SetTouchMajor(kTouchSize
);
428 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
429 EXPECT_TRUE(IsDragging());
431 event
.SetTouchMajor(kTouchSize
* 2.f
);
432 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
433 EXPECT_TRUE(IsDragging());
435 // The touch hit test region should be circular.
436 event
= MockMotionEvent(
437 MockMotionEvent::ACTION_DOWN
, event_time
, kOffset
, kOffset
);
438 event
.SetTouchMajor(kTouchSize
);
439 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
440 EXPECT_FALSE(IsDragging());
442 event
.SetTouchMajor(kTouchSize
* std::sqrt(2.f
) - 0.1f
);
443 EXPECT_FALSE(handle
.WillHandleTouchEvent(event
));
444 EXPECT_FALSE(IsDragging());
446 event
.SetTouchMajor(kTouchSize
* std::sqrt(2.f
) + 0.1f
);
447 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
448 EXPECT_TRUE(IsDragging());
450 // Ensure a touch size of 0 can still register a hit.
451 event
= MockMotionEvent(MockMotionEvent::ACTION_DOWN
,
453 kDefaultDrawableSize
/ 2.f
,
454 kDefaultDrawableSize
/ 2.f
);
455 event
.SetTouchMajor(0);
456 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
457 EXPECT_TRUE(IsDragging());
460 TEST_F(TouchHandleTest
, Tap
) {
461 TouchHandle
handle(this, TouchHandleOrientation::CENTER
);
462 handle
.SetVisible(true, TouchHandle::ANIMATION_NONE
);
464 base::TimeTicks event_time
= base::TimeTicks::Now();
466 // ACTION_CANCEL shouldn't trigger a tap.
467 MockMotionEvent
event(MockMotionEvent::ACTION_DOWN
, event_time
, 0, 0);
468 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
469 event_time
+= base::TimeDelta::FromMilliseconds(50);
470 event
= MockMotionEvent(MockMotionEvent::ACTION_CANCEL
, event_time
, 0, 0);
471 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
472 EXPECT_FALSE(GetAndResetHandleTapped());
474 // Long press shouldn't trigger a tap.
475 event
= MockMotionEvent(MockMotionEvent::ACTION_DOWN
, event_time
, 0, 0);
476 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
477 event_time
+= 2 * GetTapTimeout();
478 event
= MockMotionEvent(MockMotionEvent::ACTION_UP
, event_time
, 0, 0);
479 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
480 EXPECT_FALSE(GetAndResetHandleTapped());
482 // Only a brief tap within the slop region should trigger a tap.
483 event
= MockMotionEvent(MockMotionEvent::ACTION_DOWN
, event_time
, 0, 0);
484 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
485 event_time
+= GetTapTimeout() / 2;
486 event
= MockMotionEvent(
487 MockMotionEvent::ACTION_MOVE
, event_time
, kDefaultTapSlop
/ 2.f
, 0);
488 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
489 event
= MockMotionEvent(
490 MockMotionEvent::ACTION_UP
, event_time
, kDefaultTapSlop
/ 2.f
, 0);
491 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
492 EXPECT_TRUE(GetAndResetHandleTapped());
494 // Moving beyond the slop region shouldn't trigger a tap.
495 event
= MockMotionEvent(MockMotionEvent::ACTION_DOWN
, event_time
, 0, 0);
496 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
497 event_time
+= GetTapTimeout() / 2;
498 event
= MockMotionEvent(
499 MockMotionEvent::ACTION_MOVE
, event_time
, kDefaultTapSlop
* 2.f
, 0);
500 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
501 event
= MockMotionEvent(
502 MockMotionEvent::ACTION_UP
, event_time
, kDefaultTapSlop
* 2.f
, 0);
503 EXPECT_TRUE(handle
.WillHandleTouchEvent(event
));
504 EXPECT_FALSE(GetAndResetHandleTapped());