diff --git a/ui/touch_selection/longpress_drag_selector.cc b/ui/touch_selection/longpress_drag_selector.cc index ad68171a61910..b84c90da9e137 100644 --- a/ui/touch_selection/longpress_drag_selector.cc +++ b/ui/touch_selection/longpress_drag_selector.cc @@ -8,6 +8,13 @@ #include "ui/events/gesture_detection/motion_event.h" namespace ui { +namespace { + +gfx::Vector2dF SafeNormalize(const gfx::Vector2dF& v) { + return v.IsZero() ? v : ScaleVector2d(v, 1.f / v.Length()); +} + +} // namespace LongPressDragSelector::LongPressDragSelector( LongPressDragSelectorClient* client) @@ -70,21 +77,27 @@ bool LongPressDragSelector::WillHandleTouchEvent(const MotionEvent& event) { // If initial motion is up/down, extend the start/end selection bound. extend_selection_start = delta.y() < 0; } else { - // Otherwise extend the selection bound toward which we're moving. + // Otherwise extend the selection bound toward which we're moving, or + // the closest bound if motion is already away from both bounds. // Note that, for mixed RTL text, or for multiline selections triggered // by longpress, this may not pick the most suitable drag target - gfx::Vector2dF start_delta = selection_start - position; + gfx::Vector2dF start_delta = selection_start - longpress_drag_start_anchor_; + gfx::Vector2dF end_delta = selection_end - longpress_drag_start_anchor_; // The vectors must be normalized to make dot product comparison meaningful. - if (!start_delta.IsZero()) - start_delta.Scale(1.f / start_delta.Length()); - gfx::Vector2dF end_delta = selection_end - position; - if (!end_delta.IsZero()) - end_delta.Scale(1.f / start_delta.Length()); - - // The larger the dot product the more similar the direction. - extend_selection_start = - gfx::DotProduct(start_delta, delta) > gfx::DotProduct(end_delta, delta); + gfx::Vector2dF normalized_start_delta = SafeNormalize(start_delta); + gfx::Vector2dF normalized_end_delta = SafeNormalize(end_delta); + double start_dot_product = gfx::DotProduct(normalized_start_delta, delta); + double end_dot_product = gfx::DotProduct(normalized_end_delta, delta); + + if (start_dot_product >= 0 || end_dot_product >= 0) { + // The greater the dot product the more similar the direction. + extend_selection_start = start_dot_product > end_dot_product; + } else { + // If we're already moving away from both endpoints, pick the closest. + extend_selection_start = + start_delta.LengthSquared() < end_delta.LengthSquared(); + } } gfx::PointF extent = extend_selection_start ? selection_start : selection_end; diff --git a/ui/touch_selection/longpress_drag_selector_unittest.cc b/ui/touch_selection/longpress_drag_selector_unittest.cc index 8c126d8022bfd..885943957b65b 100644 --- a/ui/touch_selection/longpress_drag_selector_unittest.cc +++ b/ui/touch_selection/longpress_drag_selector_unittest.cc @@ -139,7 +139,7 @@ TEST_F(LongPressDragSelectorTest, BasicReverseDrag) { EXPECT_FALSE(IsDragging()); // Initiate drag motion. - EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0))); + EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 5, 0))); EXPECT_FALSE(IsDragging()); // As the initial motion is leftward, toward the selection start, the @@ -330,4 +330,38 @@ TEST_F(LongPressDragSelectorTest, SelectionDeactivated) { 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_TRUE(GetAndResetActiveStateChanged()); + SetSelection(selection_start, selection_end); + selector.OnSelectionActivated(); + 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()); +} + } // namespace ui