Index: ui/touch_selection/touch_selection_controller.cc |
diff --git a/ui/touch_selection/touch_selection_controller.cc b/ui/touch_selection/touch_selection_controller.cc |
index ec720b4280865ebdf66a4c1e49b4c73fa7b5324e..dbc7e5e47152df6b323f339c79ca4fa5ba3fb0a9 100644 |
--- a/ui/touch_selection/touch_selection_controller.cc |
+++ b/ui/touch_selection/touch_selection_controller.cc |
@@ -39,11 +39,208 @@ TouchHandleOrientation ToTouchHandleOrientation(SelectionBound::Type type) { |
} // namespace |
+class GranularityStrategy { |
+ public: |
+ virtual ~GranularityStrategy() {}; |
+ virtual void OnSelectionBoundsUpdated(const SelectionBound& start, |
+ const SelectionBound& end) {}; |
+ virtual void OnHandleDragBegin(const gfx::PointF& position, |
+ base::TimeTicks event_time, |
+ TouchSelectionController* controller) {}; |
+ virtual TextSelectionGranularity OnHandleDragUpdate( |
+ const gfx::PointF& position, |
+ const gfx::PointF& line_position, |
+ base::TimeTicks event_time, |
+ bool end_handle_dragged) { return ui::CHARACTER_GRANULARITY; }; |
+ protected: |
+ GranularityStrategy() {}; |
+}; |
+ |
+class DefaultGranularityStrategy : public GranularityStrategy { |
+ public: |
+ DefaultGranularityStrategy() {}; |
+ ~DefaultGranularityStrategy() override {}; |
+}; |
+ |
+class DirectionGranularityStrategy : public GranularityStrategy { |
+ public: |
+ DirectionGranularityStrategy() : granularity_(ui::CHARACTER_GRANULARITY) {}; |
+ |
+ ~DirectionGranularityStrategy() override {}; |
+ |
+ void OnSelectionBoundsUpdated(const SelectionBound& start, |
+ const SelectionBound& end) override { |
+// LOG(ERROR) << "Bounds updated: " |
+// << "start.y/2=" << (start.edge_bottom_rounded().y() + start.edge_top_rounded().y()) /2 |
+// << ", start.x=" << start.edge_bottom_rounded().x() |
+// << ", end.y/2=" << (end.edge_bottom_rounded().y() + end.edge_top_rounded().y()) / 2 |
+// << ", end.x=" << end.edge_bottom_rounded().x(); |
+ |
+ if (granularity_ == ui::WORD_GRANULARITY) { |
+ granularity_threshold_start_ = start; |
+ granularity_threshold_end_ = end; |
+ } |
+ }; |
+ |
+ void OnHandleDragBegin(const gfx::PointF& position, |
+ base::TimeTicks event_time, |
+ TouchSelectionController* controller) override { |
+ // TODO(mfomitchev): |
+ // Ideally we want word boundaries communicated in CompositorFrameMetadata |
+ // (see RenderWidgetHostViewAura::OnSwapCompositorFrame) |
+ granularity_threshold_start_ = controller->start(); |
+ granularity_threshold_end_ = controller->end(); |
+ farthest_dragging_handle_position_ = position; |
+ granularity_ = ui::WORD_GRANULARITY; |
+ }; |
+ |
+ // TODO(mfomitchev): perhaps this should depend on character size? |
+ // tap_slop_ is too big, uses max_touch_move_in_pixels_for_click. Perhaps |
+ // we should use span_slop? See GestureConfiguration |
+ const int kHandleDragSlop = 7; |
+ TextSelectionGranularity OnHandleDragUpdate( |
+ const gfx::PointF& position, |
+ const gfx::PointF& line_position, |
+ base::TimeTicks event_time, |
+ bool end_handle_dragged) override { |
+ if (end_handle_dragged) { |
+ if (line_position.y() > granularity_threshold_end_.edge_bottom().y()) { |
+ LOG(ERROR) << "Moved down a line - adjusting max_dragging_handle_position_"; |
+ farthest_dragging_handle_position_ = position; |
+ if (granularity_ == ui::CHARACTER_GRANULARITY) { |
+ LOG(ERROR) << "Moved down a line, switching to WORD_GRANULARITY"; |
+ granularity_ = ui::WORD_GRANULARITY; |
+ } |
+ } else if (line_position.y() < granularity_threshold_end_.edge_top().y()) { |
+ LOG(ERROR) << "Moved up a line - adjusting max_dragging_handle_position_"; |
+ farthest_dragging_handle_position_ = position; |
+ if (granularity_ == ui::CHARACTER_GRANULARITY) { |
+ granularity_ = ui::WORD_GRANULARITY; |
+ LOG(ERROR) << "Moved up a line - switching to WORD_GRANULARITY"; |
+ } |
+ } else if (position.x() > farthest_dragging_handle_position_.x()) { |
+ LOG(ERROR) << "Incrementing max_dragging_handle_position_"; |
+ farthest_dragging_handle_position_ = position; |
+ if (granularity_ == ui::CHARACTER_GRANULARITY && |
+ position.x() > granularity_threshold_end_.edge_bottom().x()) { |
+ granularity_ = ui::WORD_GRANULARITY; |
+ LOG(ERROR) << "Moved right of granularity_threshold_end_," |
+ << " switching to WORD_GRANULARITY"; |
+ } |
+ } else if (position.x() + kHandleDragSlop < |
+ farthest_dragging_handle_position_.x()) { |
+ if (granularity_ == ui::WORD_GRANULARITY) { |
+ granularity_ = ui::CHARACTER_GRANULARITY; |
+ LOG(ERROR) << "Moved left of max_dragging_handle_position_," |
+ << " switching to CHARACTER_GRANULARITY"; |
+ } |
+ } |
+ } else { //!end_handle_dragged |
+ if (line_position.y() < granularity_threshold_start_.edge_top().y() || |
+ line_position.y() > granularity_threshold_start_.edge_bottom().y()) { |
+ // Line change |
+ farthest_dragging_handle_position_ = position; |
+ granularity_ = ui::WORD_GRANULARITY; |
+ } else if (position.x() < farthest_dragging_handle_position_.x()) { |
+ farthest_dragging_handle_position_ = position; |
+ if (position.x() < granularity_threshold_start_.edge_bottom().x()) |
+ granularity_ = ui::WORD_GRANULARITY; |
+ } else if (position.x() - kHandleDragSlop > |
+ farthest_dragging_handle_position_.x()) { |
+ granularity_ = ui::CHARACTER_GRANULARITY; |
+ } |
+ } |
+ return granularity_; |
+ } |
+ |
+ private: |
+ TextSelectionGranularity granularity_; |
+ SelectionBound granularity_threshold_start_; |
+ SelectionBound granularity_threshold_end_; |
+ // If we go left of this by more than slop - switch to CHAR granularity |
+ // For right handle - farthest down/right |
+ // For left handle - farthest up/left |
+ gfx::PointF farthest_dragging_handle_position_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(DirectionGranularityStrategy); |
+}; |
+ |
+class VelocityGranularityStrategy : public GranularityStrategy { |
+ public: |
+ VelocityGranularityStrategy() : |
+ granularity_(ui::CHARACTER_GRANULARITY), |
+ avg_velocity_(0.f), |
+ filterTimeConstant_(kFilterTimeConstant), |
+ velocityGranularityThreshold_(kVelocityGranularityThreshold) {}; |
+ |
+ ~VelocityGranularityStrategy() override {}; |
+ |
+ const float kFilterTimeConstant = 0.3f; // half decay @ 300ms |
+ const float kVelocityGranularityThreshold = 80.f; |
+ |
+ void SetStrategyParameters(int halfDecayMs, int threshold) { |
+ filterTimeConstant_ = halfDecayMs / 1000.f; |
+ velocityGranularityThreshold_ = threshold; |
+ } |
+ |
+ void OnHandleDragBegin(const gfx::PointF& position, |
+ base::TimeTicks event_time, |
+ TouchSelectionController* controller) override { |
+ last_dragging_handle_position_ = position; |
+ avg_velocity_ = 0; |
+ last_event_time_ = event_time; |
+ granularity_ = ui::WORD_GRANULARITY; |
+ }; |
+ |
+ TextSelectionGranularity OnHandleDragUpdate( |
+ const gfx::PointF& position, |
+ const gfx::PointF& line_position, |
+ base::TimeTicks event_time, |
+ bool end_handle_dragged) override { |
+ float dist = (position - last_dragging_handle_position_).Length(); |
+ float dt = static_cast<float>((event_time - last_event_time_).InSecondsF()); |
+ float current_velocity = dist / dt; |
+ |
+ if (avg_velocity_ == 0) { |
+ avg_velocity_ = current_velocity; |
+ } else { |
+ float alpha = dt / (filterTimeConstant_ + dt); |
+ avg_velocity_ += (current_velocity - avg_velocity_) * alpha; |
+ } |
+ |
+ if (avg_velocity_ > velocityGranularityThreshold_) |
+ granularity_ = ui::WORD_GRANULARITY; |
+ else |
+ granularity_ = ui::CHARACTER_GRANULARITY; |
+ |
+ LOG(ERROR) << "avg_velocity=" << avg_velocity_ |
+ << ", current_velocity=" << current_velocity; |
+ |
+ last_dragging_handle_position_ = position; |
+ last_event_time_ = event_time; |
+ |
+ return granularity_; |
+ }; |
+ |
+ private: |
+ TextSelectionGranularity granularity_; |
+ gfx::PointF last_dragging_handle_position_; |
+ base::TimeTicks last_event_time_; |
+ float avg_velocity_; |
+ |
+ float filterTimeConstant_; |
+ float velocityGranularityThreshold_; |
+ |
+ |
+ DISALLOW_COPY_AND_ASSIGN(VelocityGranularityStrategy); |
+}; |
+ |
TouchSelectionController::TouchSelectionController( |
TouchSelectionControllerClient* client, |
base::TimeDelta tap_timeout, |
float tap_slop, |
- bool show_on_tap_for_empty_editable) |
+ bool show_on_tap_for_empty_editable, |
+ TextSelectionGranularityStrategy selection_granularity_strategy) |
: client_(client), |
tap_timeout_(tap_timeout), |
tap_slop_(tap_slop), |
@@ -57,19 +254,43 @@ TouchSelectionController::TouchSelectionController( |
activate_selection_automatically_(false), |
selection_empty_(false), |
selection_editable_(false), |
- temporarily_hidden_(false) { |
+ temporarily_hidden_(false), |
+ selection_granularity_strategy_(selection_granularity_strategy) { |
DCHECK(client_); |
+ switch (selection_granularity_strategy) { |
+ case GRANULARITY_STRATEGY_DEFAULT: |
+ granularity_strategy_.reset(new DefaultGranularityStrategy()); |
+ break; |
+ case GRANULARITY_STRATEGY_DIRECTION: |
+ granularity_strategy_.reset(new DirectionGranularityStrategy()); |
+ break; |
+ case GRANULARITY_STRATEGY_VELOCITY: |
+ granularity_strategy_.reset(new VelocityGranularityStrategy()); |
+ break; |
+ } |
} |
TouchSelectionController::~TouchSelectionController() { |
} |
+void TouchSelectionController::SetVelocityStrategyParameters(int halfDecayMs, |
+ int threshold) { |
+ if (selection_granularity_strategy_ != GRANULARITY_STRATEGY_VELOCITY) |
+ return; |
+ |
+ VelocityGranularityStrategy* velocityStrategy = |
+ static_cast<VelocityGranularityStrategy*>(granularity_strategy_.get()); |
+ velocityStrategy->SetStrategyParameters(halfDecayMs, threshold); |
+} |
+ |
void TouchSelectionController::OnSelectionBoundsUpdated( |
const SelectionBound& start, |
const SelectionBound& end) { |
if (start == start_ && end_ == end) |
return; |
+ granularity_strategy_->OnSelectionBoundsUpdated(start, end); |
+ |
start_ = start; |
end_ = end; |
start_orientation_ = ToTouchHandleOrientation(start_.type()); |
@@ -235,7 +456,8 @@ void TouchSelectionController::TryActivateSelection() { |
} |
} |
-void TouchSelectionController::OnHandleDragBegin(const TouchHandle& handle) { |
+void TouchSelectionController::OnHandleDragBegin(const TouchHandle& handle, |
+ base::TimeTicks event_time) { |
if (&handle == insertion_handle_.get()) { |
client_->OnSelectionEvent(INSERTION_DRAG_STARTED, handle.position()); |
return; |
@@ -245,30 +467,49 @@ void TouchSelectionController::OnHandleDragBegin(const TouchHandle& handle) { |
if (&handle == start_selection_handle_.get()) { |
base = end_selection_handle_->position() + GetEndLineOffset(); |
extent = start_selection_handle_->position() + GetStartLineOffset(); |
+ // TODO: HACK |
+ client_->SelectBetweenCoordinates(base, start_.edge_bottom_rounded() + GetStartLineOffset()); |
} else { |
base = start_selection_handle_->position() + GetStartLineOffset(); |
extent = end_selection_handle_->position() + GetEndLineOffset(); |
+ // TODO: HACK |
+ client_->SelectBetweenCoordinates(base, end_.edge_bottom_rounded() + GetEndLineOffset()); |
} |
+ granularity_strategy_->OnHandleDragBegin(handle.position(), event_time, this); |
+ |
// When moving the handle we want to move only the extent point. Before doing |
// so we must make sure that the base point is set correctly. |
- client_->SelectBetweenCoordinates(base, extent); |
+ // TODO: HACK - swallow the update |
+ //client_->SelectBetweenCoordinates(base, extent); |
+ //OnHandleDragUpdate(handle, handle.position()); |
client_->OnSelectionEvent(SELECTION_DRAG_STARTED, handle.position()); |
} |
void TouchSelectionController::OnHandleDragUpdate(const TouchHandle& handle, |
- const gfx::PointF& position) { |
+ const gfx::PointF& position, |
+ base::TimeTicks event_time) { |
+ // TODO: figure out RTL |
+ // TODO: start/end can switch! Handle! |
+ bool end_handle_dragged = &handle == end_selection_handle_.get(); |
// As the position corresponds to the bottom left point of the selection |
// bound, offset it by half the corresponding line height. |
- gfx::Vector2dF line_offset = &handle == start_selection_handle_.get() |
- ? GetStartLineOffset() |
- : GetEndLineOffset(); |
+ gfx::Vector2dF line_offset = end_handle_dragged |
+ ? GetEndLineOffset() |
+ : GetStartLineOffset(); |
+ |
gfx::PointF line_position = position + line_offset; |
+ |
if (&handle == insertion_handle_.get()) { |
client_->MoveCaret(line_position); |
} else { |
- client_->MoveRangeSelectionExtent(line_position, ui::CHARACTER_GRANULARITY); |
+ TextSelectionGranularity granularity = |
+ granularity_strategy_->OnHandleDragUpdate(position, |
+ line_position, |
+ event_time, |
+ end_handle_dragged); |
+ client_->MoveRangeSelectionExtent(line_position, granularity); |
} |
} |