Chromium Code Reviews| Index: ui/touch_selection/longpress_drag_selector.cc |
| diff --git a/ui/touch_selection/longpress_drag_selector.cc b/ui/touch_selection/longpress_drag_selector.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..6055d4e0057e8673f34d9685287dcb2a690d9475 |
| --- /dev/null |
| +++ b/ui/touch_selection/longpress_drag_selector.cc |
| @@ -0,0 +1,138 @@ |
| +// Copyright 2015 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 "ui/events/gesture_detection/motion_event.h" |
| + |
| +namespace ui { |
| +namespace { |
| + |
| +const int kLongPressTimeEpsilonMicros = 10; |
| + |
| +} // namespace |
| + |
| +LongPressDragSelector::LongPressDragSelector( |
| + TouchSelectionDraggableClient* client, |
| + float slop_length) |
| + : client_(client), slop_length_(slop_length), state_(INACTIVE) { |
| +} |
| + |
| +LongPressDragSelector::~LongPressDragSelector() { |
| +} |
| + |
| +bool LongPressDragSelector::WillHandleTouchEvent(const MotionEvent& event) { |
| + switch (event.GetAction()) { |
| + case MotionEvent::ACTION_DOWN: |
| + touch_down_position_.SetPoint(event.GetX(), event.GetY()); |
| + touch_down_time_ = event.GetEventTime(); |
| + longpress_drag_start_position_ = gfx::PointF(); |
| + SetState(LONGPRESS_PENDING); |
| + return false; |
| + |
| + case MotionEvent::ACTION_UP: |
| + case MotionEvent::ACTION_CANCEL: |
| + SetState(INACTIVE); |
| + return false; |
| + |
| + case MotionEvent::ACTION_MOVE: |
| + break; |
| + |
| + default: |
| + return false; |
| + } |
| + |
| + if (state_ != DRAG_PENDING && state_ != DRAGGING) |
| + return false; |
| + |
| + gfx::PointF position(event.GetX(), event.GetY()); |
| + if (state_ == DRAGGING) { |
| + gfx::PointF drag_position = position + longpress_drag_selection_offset_; |
| + client_->OnDragUpdate(*this, drag_position); |
| + return true; |
| + } |
| + |
| + // We can't use |touch_down_position_| as the offset anchor, as |
| + // showing the selection UI may have shifted the motion coordinates. |
| + if (longpress_drag_start_position_.IsOrigin()) { |
| + longpress_drag_start_position_ = position; |
| + return true; |
| + } |
| + |
| + // Allow an additional slop affordance after the longpress occurs. |
| + gfx::Vector2dF delta = position - longpress_drag_start_position_; |
| + if (delta.LengthSquared() < (slop_length_ * slop_length_)) |
| + return true; |
| + |
| + gfx::PointF base = selection_start_; |
| + gfx::PointF extent = selection_end_; |
| + if (std::abs(delta.y()) > std::abs(delta.x())) { |
| + // If initial motion is upward, extend the starting selection bound. |
|
mfomitchev
2015/04/23 21:05:53
By the similar logic, wouldn't we want to extend t
jdduke (slow)
2015/04/27 20:24:25
We do, the exceptional case is dragging 'backward'
mfomitchev
2015/04/28 02:17:30
Ah, right, I misread the code.
|
| + if (delta.y() < 0) |
| + std::swap(base, extent); |
| + } else { |
| + // Otherwise extend the selection bound toward which we're moving. |
| + gfx::Vector2dF start_delta = selection_start_ - position; |
| + 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()); |
| + if (gfx::DotProduct(start_delta, delta) > |
|
mfomitchev
2015/04/23 21:05:53
Heh. Smart.
Long-press is going to select a single
jdduke (slow)
2015/04/27 20:24:25
Yeah, without awareness of text direction, making
mfomitchev
2015/04/28 02:17:30
We can already make the decision based on whether
jdduke (slow)
2015/04/28 20:37:48
Hmm, yeah, the math isn't quite as clear.
That s
|
| + gfx::DotProduct(end_delta, delta)) { |
| + std::swap(base, extent); |
| + } |
| + } |
| + longpress_drag_selection_offset_ = extent - position; |
| + client_->OnDragBegin(*this, extent); |
| + SetState(DRAGGING); |
| + return true; |
| +} |
| + |
| +void LongPressDragSelector::OnLongPressEvent(base::TimeTicks event_time, |
| + const gfx::PointF& position) { |
| + // We have no guarantees that the current gesture stream is aligned with the |
| + // observed touch stream. We only know that the gesture sequence is downstream |
| + // from the touch sequence. Using a time/distance heuristic helps ensure that |
| + // the observed longpress corresponds to the active touch sequence. |
| + if (state_ == LONGPRESS_PENDING && |
| + // Use a small time epsilon to account for floating point time conversion. |
| + (touch_down_time_ - event_time) < |
| + base::TimeDelta::FromMicroseconds(kLongPressTimeEpsilonMicros) && |
| + ((touch_down_position_ - position).LengthSquared() < |
| + (slop_length_ * slop_length_))) { |
| + SetState(SELECTION_PENDING); |
| + } |
| +} |
| + |
| +void LongPressDragSelector::OnSelectionActivated(const gfx::PointF& start, |
| + const gfx::PointF& end) { |
| + if (state_ == SELECTION_PENDING) { |
| + SetState(DRAG_PENDING); |
| + selection_start_ = start; |
| + selection_end_ = end; |
| + } |
| +} |
| + |
| +void LongPressDragSelector::OnSelectionDeactivated() { |
| + SetState(INACTIVE); |
| +} |
| + |
| +bool LongPressDragSelector::IsActive() const { |
|
mfomitchev
2015/04/23 21:05:53
This is similar to is_dragging() in TouchHandle. P
jdduke (slow)
2015/04/27 20:24:25
Hmm, the current result would be slightly misleadi
mfomitchev
2015/04/28 02:17:30
Sorry, I should've clarified my comment. I was sug
jdduke (slow)
2015/04/28 20:37:48
IsActive() sounds good to me.
|
| + return state_ != INACTIVE && state_ != LONGPRESS_PENDING; |
| +} |
| + |
| +void LongPressDragSelector::SetState(SelectionState state) { |
| + if (state_ == state) |
| + return; |
| + |
| + const bool was_dragging = state_ == DRAGGING; |
| + state_ = state; |
| + |
| + // TODO(jdduke): Add UMA for tracking relative longpress drag frequency. |
| + if (was_dragging) |
| + client_->OnDragEnd(*this); |
| +} |
| + |
| +} // namespace ui |