| 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..ad68171a61910f198ed457e0003466b8895cf967
|
| --- /dev/null
|
| +++ b/ui/touch_selection/longpress_drag_selector.cc
|
| @@ -0,0 +1,141 @@
|
| +// 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 "base/auto_reset.h"
|
| +#include "ui/events/gesture_detection/motion_event.h"
|
| +
|
| +namespace ui {
|
| +
|
| +LongPressDragSelector::LongPressDragSelector(
|
| + LongPressDragSelectorClient* client)
|
| + : client_(client),
|
| + state_(INACTIVE),
|
| + has_longpress_drag_start_anchor_(false) {
|
| +}
|
| +
|
| +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();
|
| + has_longpress_drag_start_anchor_ = false;
|
| + 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 (!has_longpress_drag_start_anchor_) {
|
| + has_longpress_drag_start_anchor_ = true;
|
| + longpress_drag_start_anchor_ = position;
|
| + return true;
|
| + }
|
| +
|
| + // Allow an additional slop affordance after the longpress occurs.
|
| + gfx::Vector2dF delta = position - longpress_drag_start_anchor_;
|
| + if (client_->IsWithinTapSlop(delta))
|
| + return true;
|
| +
|
| + gfx::PointF selection_start = client_->GetSelectionStart();
|
| + gfx::PointF selection_end = client_->GetSelectionEnd();
|
| + bool extend_selection_start = false;
|
| + if (std::abs(delta.y()) > std::abs(delta.x())) {
|
| + // 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.
|
| + // 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;
|
| +
|
| + // 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::PointF extent = extend_selection_start ? selection_start : selection_end;
|
| + longpress_drag_selection_offset_ = extent - position;
|
| + client_->OnDragBegin(*this, extent);
|
| + SetState(DRAGGING);
|
| + return true;
|
| +}
|
| +
|
| +bool LongPressDragSelector::IsActive() const {
|
| + return state_ != INACTIVE && state_ != LONGPRESS_PENDING;
|
| +}
|
| +
|
| +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 &&
|
| + // Ensure the down event occurs *before* the longpress event. Use a
|
| + // small time epsilon to account for floating point time conversion.
|
| + (touch_down_time_ < event_time + base::TimeDelta::FromMicroseconds(10)) &&
|
| + client_->IsWithinTapSlop(touch_down_position_ - position)) {
|
| + SetState(SELECTION_PENDING);
|
| + }
|
| +}
|
| +
|
| +void LongPressDragSelector::OnSelectionActivated() {
|
| + if (state_ == SELECTION_PENDING)
|
| + SetState(DRAG_PENDING);
|
| +}
|
| +
|
| +void LongPressDragSelector::OnSelectionDeactivated() {
|
| + SetState(INACTIVE);
|
| +}
|
| +
|
| +void LongPressDragSelector::SetState(SelectionState state) {
|
| + if (state_ == state)
|
| + return;
|
| +
|
| + const bool was_dragging = state_ == DRAGGING;
|
| + const bool was_active = IsActive();
|
| + state_ = state;
|
| +
|
| + // TODO(jdduke): Add UMA for tracking relative longpress drag frequency.
|
| + if (was_dragging)
|
| + client_->OnDragEnd(*this);
|
| +
|
| + if (was_active != IsActive())
|
| + client_->OnLongPressDragActiveStateChanged();
|
| +}
|
| +
|
| +} // namespace ui
|
|
|