| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/touch_selection/longpress_drag_selector.h" |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/events/test/motion_event_test_utils.h" |
| |
| using ui::test::MockMotionEvent; |
| |
| namespace ui { |
| namespace { |
| |
| const double kSlop = 10.; |
| |
| } // namespace |
| |
| class LongPressDragSelectorTest : public testing::Test, |
| public LongPressDragSelectorClient { |
| public: |
| LongPressDragSelectorTest() |
| : dragging_(false), active_state_changed_(false) {} |
| |
| ~LongPressDragSelectorTest() override {} |
| |
| void SetSelection(const gfx::PointF& start, const gfx::PointF& end) { |
| selection_start_ = start; |
| selection_end_ = end; |
| } |
| |
| bool GetAndResetActiveStateChanged() { |
| bool active_state_changed = active_state_changed_; |
| active_state_changed_ = false; |
| return active_state_changed; |
| } |
| |
| bool IsDragging() const { return dragging_; } |
| const gfx::PointF& DragPosition() const { return drag_position_; } |
| |
| // LongPressDragSelectorClient implementation. |
| void OnDragBegin(const TouchSelectionDraggable& handler, |
| const gfx::PointF& drag_position) override { |
| dragging_ = true; |
| drag_position_ = drag_position; |
| } |
| |
| void OnDragUpdate(const TouchSelectionDraggable& handler, |
| const gfx::PointF& drag_position) override { |
| drag_position_ = drag_position; |
| } |
| |
| void OnDragEnd(const TouchSelectionDraggable& handler) override { |
| dragging_ = false; |
| } |
| |
| bool IsWithinTapSlop(const gfx::Vector2dF& delta) const override { |
| return delta.LengthSquared() < (kSlop * kSlop); |
| } |
| |
| void OnLongPressDragActiveStateChanged() override { |
| active_state_changed_ = true; |
| } |
| |
| gfx::PointF GetSelectionStart() const override { return selection_start_; } |
| |
| gfx::PointF GetSelectionEnd() const override { return selection_end_; } |
| |
| private: |
| bool dragging_; |
| bool active_state_changed_; |
| gfx::PointF drag_position_; |
| |
| gfx::PointF selection_start_; |
| gfx::PointF selection_end_; |
| }; |
| |
| TEST_F(LongPressDragSelectorTest, BasicDrag) { |
| LongPressDragSelector selector(this); |
| MockMotionEvent event; |
| |
| // Start a touch sequence. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0))); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| |
| // Activate a longpress-triggered selection. |
| gfx::PointF selection_start(0, 10); |
| gfx::PointF selection_end(10, 10); |
| selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF()); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| |
| // Motion should not be consumed until a selection is detected. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0))); |
| SetSelection(selection_start, selection_end); |
| selector.OnSelectionActivated(); |
| EXPECT_TRUE(GetAndResetActiveStateChanged()); |
| EXPECT_FALSE(IsDragging()); |
| |
| // Initiate drag motion. Note that the first move event after activation is |
| // used to initialize the drag start anchor. |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0))); |
| EXPECT_FALSE(IsDragging()); |
| |
| // The first slop exceeding motion will start the drag. As the motion is |
| // downward, the end selection point should be moved. |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop))); |
| EXPECT_TRUE(IsDragging()); |
| EXPECT_EQ(selection_end, DragPosition()); |
| |
| // Subsequent motion will extend the selection. |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2))); |
| EXPECT_TRUE(IsDragging()); |
| EXPECT_EQ(selection_end + gfx::Vector2dF(0, kSlop), DragPosition()); |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 3))); |
| EXPECT_TRUE(IsDragging()); |
| EXPECT_EQ(selection_end + gfx::Vector2dF(0, kSlop * 2), DragPosition()); |
| |
| // Release the touch sequence, ending the drag. The selector will never |
| // consume the start/end events, only move events after a longpress. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint())); |
| EXPECT_FALSE(IsDragging()); |
| EXPECT_TRUE(GetAndResetActiveStateChanged()); |
| } |
| |
| TEST_F(LongPressDragSelectorTest, BasicReverseDrag) { |
| LongPressDragSelector selector(this); |
| MockMotionEvent event; |
| |
| // Start a touch sequence. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0))); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| |
| // Activate a longpress-triggered selection. |
| gfx::PointF selection_start(0, 10); |
| gfx::PointF selection_end(10, 10); |
| selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF()); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| SetSelection(selection_start, selection_end); |
| selector.OnSelectionActivated(); |
| EXPECT_TRUE(GetAndResetActiveStateChanged()); |
| EXPECT_FALSE(IsDragging()); |
| |
| // Initiate drag motion. |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 5, 0))); |
| EXPECT_FALSE(IsDragging()); |
| |
| // As the initial motion is leftward, toward the selection start, the |
| // selection start should be the drag point. |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, -kSlop, 0))); |
| EXPECT_TRUE(IsDragging()); |
| EXPECT_EQ(selection_start, DragPosition()); |
| |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, -kSlop))); |
| EXPECT_TRUE(IsDragging()); |
| EXPECT_EQ(selection_start + gfx::Vector2dF(kSlop, -kSlop), DragPosition()); |
| |
| // Release the touch sequence, ending the drag. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint())); |
| EXPECT_FALSE(IsDragging()); |
| EXPECT_TRUE(GetAndResetActiveStateChanged()); |
| } |
| |
| TEST_F(LongPressDragSelectorTest, NoActiveTouch) { |
| LongPressDragSelector selector(this); |
| MockMotionEvent event; |
| |
| // Activate a longpress-triggered selection. |
| gfx::PointF selection_start(0, 10); |
| gfx::PointF selection_end(10, 10); |
| selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF()); |
| SetSelection(selection_start, selection_end); |
| selector.OnSelectionActivated(); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| EXPECT_FALSE(IsDragging()); |
| |
| // Start a new touch sequence; it shouldn't initiate selection drag as there |
| // was no active touch sequence when the longpress selection started. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0))); |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0))); |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop))); |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2))); |
| EXPECT_FALSE(IsDragging()); |
| EXPECT_EQ(gfx::PointF(), DragPosition()); |
| } |
| |
| TEST_F(LongPressDragSelectorTest, NoLongPress) { |
| LongPressDragSelector selector(this); |
| MockMotionEvent event; |
| |
| // Start a touch sequence. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0))); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| |
| // Activate a selection without a preceding longpress. |
| gfx::PointF selection_start(0, 10); |
| gfx::PointF selection_end(10, 10); |
| SetSelection(selection_start, selection_end); |
| selector.OnSelectionActivated(); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| EXPECT_FALSE(IsDragging()); |
| |
| // Touch movement should not initiate selection drag. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0))); |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop))); |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2))); |
| EXPECT_FALSE(IsDragging()); |
| EXPECT_EQ(gfx::PointF(), DragPosition()); |
| } |
| |
| TEST_F(LongPressDragSelectorTest, NoValidLongPress) { |
| LongPressDragSelector selector(this); |
| MockMotionEvent event; |
| |
| // Start a touch sequence. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0))); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| |
| gfx::PointF selection_start(0, 10); |
| gfx::PointF selection_end(10, 10); |
| SetSelection(selection_start, selection_end); |
| |
| // Activate a longpress-triggered selection, but at a time before the current |
| // touch down event. |
| selector.OnLongPressEvent( |
| event.GetEventTime() - base::TimeDelta::FromSeconds(1), gfx::PointF()); |
| selector.OnSelectionActivated(); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| EXPECT_FALSE(IsDragging()); |
| |
| // Activate a longpress-triggered selection, but at a place different than the |
| // current touch down event. |
| selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF(kSlop, 0)); |
| selector.OnSelectionActivated(); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| EXPECT_FALSE(IsDragging()); |
| |
| // Touch movement should not initiate selection drag. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0))); |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop))); |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2))); |
| EXPECT_FALSE(IsDragging()); |
| EXPECT_EQ(gfx::PointF(), DragPosition()); |
| } |
| |
| TEST_F(LongPressDragSelectorTest, NoSelection) { |
| LongPressDragSelector selector(this); |
| MockMotionEvent event; |
| |
| // Start a touch sequence. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0))); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| |
| // Trigger a longpress. |
| selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF()); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| EXPECT_FALSE(IsDragging()); |
| |
| // Touch movement should not initiate selection drag, as there is no active |
| // selection. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0))); |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop))); |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2))); |
| EXPECT_FALSE(IsDragging()); |
| EXPECT_EQ(gfx::PointF(), DragPosition()); |
| |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint())); |
| } |
| |
| TEST_F(LongPressDragSelectorTest, NoDragMotion) { |
| LongPressDragSelector selector(this); |
| MockMotionEvent event; |
| |
| // Start a touch sequence. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0))); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| |
| // Activate a longpress-triggered selection. |
| selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF()); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| gfx::PointF selection_start(0, 10); |
| gfx::PointF selection_end(10, 10); |
| SetSelection(selection_start, selection_end); |
| selector.OnSelectionActivated(); |
| EXPECT_TRUE(GetAndResetActiveStateChanged()); |
| EXPECT_FALSE(IsDragging()); |
| |
| // Touch movement within the slop region should not initiate selection drag. |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0))); |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop / 2))); |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, -kSlop / 2))); |
| EXPECT_FALSE(IsDragging()); |
| EXPECT_EQ(gfx::PointF(), DragPosition()); |
| |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint())); |
| EXPECT_TRUE(GetAndResetActiveStateChanged()); |
| } |
| |
| TEST_F(LongPressDragSelectorTest, SelectionDeactivated) { |
| LongPressDragSelector selector(this); |
| MockMotionEvent event; |
| |
| // Start a touch sequence. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0))); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| |
| // Activate a longpress-triggered selection. |
| selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF()); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| gfx::PointF selection_start(0, 10); |
| gfx::PointF selection_end(10, 10); |
| SetSelection(selection_start, selection_end); |
| selector.OnSelectionActivated(); |
| EXPECT_TRUE(GetAndResetActiveStateChanged()); |
| EXPECT_FALSE(IsDragging()); |
| |
| // Start a drag selection. |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0))); |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop))); |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2))); |
| EXPECT_TRUE(IsDragging()); |
| |
| // Clearing the selection should force an end to the drag. |
| selector.OnSelectionDeactivated(); |
| EXPECT_TRUE(GetAndResetActiveStateChanged()); |
| EXPECT_FALSE(IsDragging()); |
| |
| // Subsequent motion should not be consumed. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0))); |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop))); |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2))); |
| EXPECT_FALSE(IsDragging()); |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint())); |
| } |
| |
| TEST_F(LongPressDragSelectorTest, DragFast) { |
| LongPressDragSelector selector(this); |
| MockMotionEvent event; |
| |
| // Start a touch sequence. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0))); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| |
| // Activate a longpress-triggered selection. |
| gfx::PointF selection_start(0, 10); |
| gfx::PointF selection_end(10, 10); |
| selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF()); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| SetSelection(selection_start, selection_end); |
| selector.OnSelectionActivated(); |
| EXPECT_TRUE(GetAndResetActiveStateChanged()); |
| EXPECT_FALSE(IsDragging()); |
| |
| // Initiate drag motion. |
| EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 15, 5))); |
| EXPECT_FALSE(IsDragging()); |
| |
| // As the initial motion exceeds both endpoints, the closer bound should |
| // be used for dragging, in this case the selection end. |
| EXPECT_TRUE(selector.WillHandleTouchEvent( |
| event.MovePoint(0, 15.f + kSlop * 2.f, 5.f + kSlop))); |
| EXPECT_TRUE(IsDragging()); |
| EXPECT_EQ(selection_end, DragPosition()); |
| |
| // Release the touch sequence, ending the drag. |
| EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint())); |
| EXPECT_FALSE(IsDragging()); |
| EXPECT_TRUE(GetAndResetActiveStateChanged()); |
| } |
| |
| TEST_F(LongPressDragSelectorTest, ScrollAfterLongPress) { |
| LongPressDragSelector selector(this); |
| MockMotionEvent event; |
| gfx::PointF touch_point(0, 0); |
| |
| // Start a touch sequence. |
| EXPECT_FALSE(selector.WillHandleTouchEvent( |
| event.PressPoint(touch_point.x(), touch_point.y()))); |
| |
| // Long-press and hold down. |
| selector.OnLongPressEvent(event.GetEventTime(), touch_point); |
| |
| // Scroll the page. This should cancel long-press drag gesture. |
| touch_point.Offset(0, 2 * kSlop); |
| EXPECT_FALSE(selector.WillHandleTouchEvent( |
| event.MovePoint(0, touch_point.x(), touch_point.y()))); |
| selector.OnScrollBeginEvent(); |
| |
| // Now if the selection is activated, because long-press drag gesture was |
| // canceled, active state of the long-press selector should not change. |
| selector.OnSelectionActivated(); |
| EXPECT_FALSE(GetAndResetActiveStateChanged()); |
| |
| // Release the touch sequence. |
| selector.WillHandleTouchEvent(event.ReleasePoint()); |
| } |
| |
| } // namespace ui |