Chromium Code Reviews| Index: ui/events/gesture_detection/scale_gesture_detector.cc |
| diff --git a/ui/events/gesture_detection/scale_gesture_detector.cc b/ui/events/gesture_detection/scale_gesture_detector.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..2c1ee198e6d0649eaf6e858205624b75f1ad4ebd |
| --- /dev/null |
| +++ b/ui/events/gesture_detection/scale_gesture_detector.cc |
| @@ -0,0 +1,364 @@ |
| +// 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/events/gesture_detection/scale_gesture_detector.h" |
| + |
| +#include <limits.h> |
| +#include <math.h> |
| + |
| +#include "base/logging.h" |
| +#include "ui/events/gesture_detection/motion_event.h" |
| + |
| +using base::TimeDelta; |
| +using base::TimeTicks; |
| + |
| +namespace ui { |
| +namespace { |
| + |
| +const TimeDelta TOUCH_STABILIZE_TIME = TimeDelta::FromMilliseconds(128); |
|
Xianzhu
2014/02/19 22:01:56
Global variables of class type are forbidden (http
jdduke (slow)
2014/02/20 18:01:09
OK, it's a shame we can't use constexpr like the r
|
| + |
| +const float SCALE_FACTOR = .5f; |
|
Xianzhu
2014/02/19 22:01:56
s/SCALE_FACTOR/kScaleFactor/
jdduke (slow)
2014/02/20 18:01:09
Done.
|
| + |
| +} // namespace |
| + |
| +ScaleGestureDetector::Config::Config() |
| + : quick_scale_enabled(false), |
| + min_scaling_touch_major(48), |
| + min_scaling_span(200) {} |
| + |
| +ScaleGestureDetector::Config::~Config() {} |
| + |
| +bool ScaleGestureDetector::SimpleOnScaleGestureListener::OnScale( |
| + const ScaleGestureDetector&) { |
| + return false; |
| +} |
| + |
| +bool ScaleGestureDetector::SimpleOnScaleGestureListener::OnScaleBegin( |
| + const ScaleGestureDetector&) { |
| + return true; |
| +} |
| + |
| +void ScaleGestureDetector::SimpleOnScaleGestureListener::OnScaleEnd( |
| + const ScaleGestureDetector&) {} |
| + |
| +ScaleGestureDetector::ScaleGestureDetector(const Config& config, |
| + OnScaleGestureListener* listener) |
| + : listener_(listener), |
| + config_(config), |
| + focus_x_(0), |
| + focus_y_(0), |
| + quick_scale_enabled_(false), |
| + curr_span_(0), |
| + prev_span_(0), |
| + initial_span_(0), |
| + curr_span_x_(0), |
| + curr_span_y_(0), |
| + prev_span_x_(0), |
| + prev_span_y_(0), |
| + in_progress_(0), |
| + span_slop_(0), |
| + min_span_(0), |
| + touch_upper_(0), |
| + touch_lower_(0), |
| + touch_history_last_accepted_(0), |
| + touch_history_direction_(0), |
| + touch_min_major_(0), |
| + double_tap_focus_x_(0), |
| + double_tap_focus_y_(0), |
| + double_tap_mode_(DOUBLE_TAP_MODE_NONE), |
| + event_before_or_above_starting_gesture_event_(false) { |
| + DCHECK(listener_); |
| + span_slop_ = config.gesture_detector_config.scaled_touch_slop * 2; |
| + touch_min_major_ = config.min_scaling_touch_major; |
| + min_span_ = config.min_scaling_span; |
| + SetQuickScaleEnabled(config.quick_scale_enabled); |
| +} |
| + |
| +ScaleGestureDetector::~ScaleGestureDetector() {} |
| + |
| +bool ScaleGestureDetector::OnTouchEvent(const MotionEvent& event) { |
| + curr_time_ = event.GetEventTime(); |
| + |
| + const int action = event.GetAction(); |
| + |
| + // Forward the event to check for double tap gesture |
| + if (quick_scale_enabled_) { |
| + DCHECK(gesture_detector_); |
| + gesture_detector_->OnTouchEvent(event); |
| + } |
| + |
| + const bool stream_complete = |
| + action == MotionEvent::ACTION_UP || action == MotionEvent::ACTION_CANCEL; |
| + |
| + if (action == MotionEvent::ACTION_DOWN || stream_complete) { |
| + // Reset any scale in progress with the listener. |
| + // If it's an ACTION_DOWN we're beginning a new event stream. |
| + // This means the app probably didn't give us all the events. Shame on it. |
| + if (in_progress_) { |
| + listener_->OnScaleEnd(*this); |
| + in_progress_ = false; |
| + initial_span_ = 0; |
| + double_tap_mode_ = DOUBLE_TAP_MODE_NONE; |
| + } else if (double_tap_mode_ == DOUBLE_TAP_MODE_IN_PROGRESS && |
| + stream_complete) { |
| + in_progress_ = false; |
| + initial_span_ = 0; |
| + double_tap_mode_ = DOUBLE_TAP_MODE_NONE; |
| + } |
| + |
| + if (stream_complete) { |
| + ClearTouchHistory(); |
| + return true; |
| + } |
| + } |
| + |
| + const bool config_changed = action == MotionEvent::ACTION_DOWN || |
| + action == MotionEvent::ACTION_POINTER_UP || |
| + action == MotionEvent::ACTION_POINTER_DOWN; |
| + |
| + const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP; |
| + const int skip_index = pointer_up ? event.GetActionIndex() : -1; |
| + |
| + // Determine focal point |
| + float sum_x = 0, sum_y = 0; |
| + const int count = event.GetPointerCount(); |
| + const int div = pointer_up ? count - 1 : count; |
| + float focus_x; |
| + float focus_y; |
| + if (double_tap_mode_ == DOUBLE_TAP_MODE_IN_PROGRESS) { |
| + // In double tap mode, the focal pt is always where the double tap |
| + // gesture started |
| + focus_x = double_tap_focus_x_; |
| + focus_y = double_tap_focus_y_; |
| + if (event.GetY() < focus_y) { |
| + event_before_or_above_starting_gesture_event_ = true; |
| + } else { |
| + event_before_or_above_starting_gesture_event_ = false; |
| + } |
| + } else { |
| + for (int i = 0; i < count; i++) { |
| + if (skip_index == i) |
| + continue; |
| + sum_x += event.GetX(i); |
| + sum_y += event.GetY(i); |
| + } |
| + |
| + focus_x = sum_x / div; |
| + focus_y = sum_y / div; |
| + } |
| + |
| + AddTouchHistory(event); |
| + |
| + // Determine average deviation from focal point |
| + float dev_sum_x = 0, dev_sum_y = 0; |
| + for (int i = 0; i < count; i++) { |
| + if (skip_index == i) |
| + continue; |
| + |
| + // Convert the resulting diameter into a radius. |
| + const float touch_size = touch_history_last_accepted_ / 2; |
| + dev_sum_x += std::abs(event.GetX(i) - focus_x) + touch_size; |
| + dev_sum_y += std::abs(event.GetY(i) - focus_y) + touch_size; |
| + } |
| + const float dev_x = dev_sum_x / div; |
| + const float dev_y = dev_sum_y / div; |
| + |
| + // Span is the average distance between touch points through the focal point; |
| + // i.e. the diameter of the circle with a radius of the average deviation from |
| + // the focal point. |
| + const float span_x = dev_x * 2; |
| + const float span_y = dev_y * 2; |
| + float span; |
| + if (InDoubleTapMode()) { |
| + span = span_y; |
| + } else { |
| + span = std::sqrt(span_x * span_x + span_y * span_y); |
| + } |
| + |
| + // Dispatch begin/end events as needed. |
| + // If the configuration changes, notify the app to reset its current state by |
| + // beginning a fresh scale event stream. |
| + const bool was_in_progress = in_progress_; |
| + focus_x_ = focus_x; |
| + focus_y_ = focus_y; |
| + if (!InDoubleTapMode() && in_progress_ && |
| + (span < min_span_ || config_changed)) { |
| + listener_->OnScaleEnd(*this); |
| + in_progress_ = false; |
| + initial_span_ = span; |
| + double_tap_mode_ = DOUBLE_TAP_MODE_NONE; |
| + } |
| + if (config_changed) { |
| + prev_span_x_ = curr_span_x_ = span_x; |
| + prev_span_y_ = curr_span_y_ = span_y; |
| + initial_span_ = prev_span_ = curr_span_ = span; |
| + } |
| + |
| + const int min_span = InDoubleTapMode() ? span_slop_ : min_span_; |
| + if (!in_progress_ && span >= min_span && |
| + (was_in_progress || std::abs(span - initial_span_) > span_slop_)) { |
| + prev_span_x_ = curr_span_x_ = span_x; |
| + prev_span_y_ = curr_span_y_ = span_y; |
| + prev_span_ = curr_span_ = span; |
| + prev_time_ = curr_time_; |
| + in_progress_ = listener_->OnScaleBegin(*this); |
| + } |
| + |
| + // Handle motion; focal point and span/scale factor are changing. |
| + if (action == MotionEvent::ACTION_MOVE) { |
| + curr_span_x_ = span_x; |
| + curr_span_y_ = span_y; |
| + curr_span_ = span; |
| + |
| + bool update_prev = true; |
| + |
| + if (in_progress_) { |
| + update_prev = listener_->OnScale(*this); |
| + } |
| + |
| + if (update_prev) { |
| + prev_span_x_ = curr_span_x_; |
| + prev_span_y_ = curr_span_y_; |
| + prev_span_ = curr_span_; |
| + prev_time_ = curr_time_; |
| + } |
| + } |
| + |
| + return true; |
| +} |
| + |
| +void ScaleGestureDetector::SetQuickScaleEnabled(bool scales) { |
| + quick_scale_enabled_ = scales; |
| + if (quick_scale_enabled_ && !gesture_detector_) { |
| + gesture_detector_.reset( |
| + new GestureDetector(config_.gesture_detector_config, this, this)); |
| + } |
| +} |
| + |
| +bool ScaleGestureDetector::IsQuickScaleEnabled() const { |
| + return quick_scale_enabled_; |
| +} |
| + |
| +bool ScaleGestureDetector::IsInProgress() const { return in_progress_; } |
| + |
| +float ScaleGestureDetector::GetFocusX() const { return focus_x_; } |
| + |
| +float ScaleGestureDetector::GetFocusY() const { return focus_y_; } |
| + |
| +float ScaleGestureDetector::GetCurrentSpan() const { return curr_span_; } |
| + |
| +float ScaleGestureDetector::GetCurrentSpanX() const { return curr_span_x_; } |
| + |
| +float ScaleGestureDetector::GetCurrentSpanY() const { return curr_span_y_; } |
| + |
| +float ScaleGestureDetector::GetPreviousSpan() const { return prev_span_; } |
| + |
| +float ScaleGestureDetector::GetPreviousSpanX() const { return prev_span_x_; } |
| + |
| +float ScaleGestureDetector::GetPreviousSpanY() const { return prev_span_y_; } |
| + |
| +float ScaleGestureDetector::GetScaleFactor() const { |
| + if (InDoubleTapMode()) { |
| + // Drag is moving up; the further away from the gesture |
| + // start, the smaller the span should be, the closer, |
| + // the larger the span, and therefore the larger the scale |
| + const bool scale_up = (event_before_or_above_starting_gesture_event_ && |
| + (curr_span_ < prev_span_)) || |
| + (!event_before_or_above_starting_gesture_event_ && |
| + (curr_span_ > prev_span_)); |
| + const float span_diff = |
| + (std::abs(1.f - (curr_span_ / prev_span_)) * SCALE_FACTOR); |
| + return prev_span_ <= 0 ? 1.f |
| + : (scale_up ? (1.f + span_diff) : (1.f - span_diff)); |
| + } |
| + return prev_span_ > 0 ? curr_span_ / prev_span_ : 1; |
| +} |
| + |
| +base::TimeDelta ScaleGestureDetector::GetTimeDelta() const { |
| + return curr_time_ - prev_time_; |
| +} |
| + |
| +base::TimeTicks ScaleGestureDetector::GetEventTime() const { |
| + return curr_time_; |
| +} |
| + |
| +bool ScaleGestureDetector::OnDoubleTap(const MotionEvent& ev) { |
| + // Double tap: start watching for a swipe |
| + double_tap_focus_x_ = ev.GetX(); |
| + double_tap_focus_y_ = ev.GetY(); |
| + double_tap_mode_ = DOUBLE_TAP_MODE_IN_PROGRESS; |
| + return true; |
| +} |
| + |
| +void ScaleGestureDetector::AddTouchHistory(const MotionEvent& ev) { |
| + const base::TimeTicks current_time = base::TimeTicks::Now(); |
| + const int count = ev.GetPointerCount(); |
| + bool accept = |
| + current_time - touch_history_last_accepted_time_ >= TOUCH_STABILIZE_TIME; |
| + float total = 0; |
| + int sample_count = 0; |
| + for (int i = 0; i < count; i++) { |
| + const bool has_last_accepted = !std::isnan(touch_history_last_accepted_); |
| + const int history_size = ev.GetHistorySize(); |
| + const int pointersample_count = history_size + 1; |
| + for (int h = 0; h < pointersample_count; h++) { |
| + float major; |
| + if (h < history_size) { |
| + major = ev.GetHistoricalTouchMajor(i, h); |
| + } else { |
| + major = ev.GetTouchMajor(i); |
| + } |
| + if (major < touch_min_major_) |
| + major = touch_min_major_; |
| + total += major; |
| + |
| + if (std::isnan(touch_upper_) || major > touch_upper_) { |
| + touch_upper_ = major; |
| + } |
| + if (std::isnan(touch_lower_) || major < touch_lower_) { |
| + touch_lower_ = major; |
| + } |
| + |
| + if (has_last_accepted) { |
| + const float major_delta = major - touch_history_last_accepted_; |
| + const int direction_sig = |
| + major_delta > 0 ? 1 : (major_delta < 0 ? -1 : 0); |
| + if (direction_sig != touch_history_direction_ || |
| + (direction_sig == 0 && touch_history_direction_ == 0)) { |
| + touch_history_direction_ = direction_sig; |
| + touch_history_last_accepted_time_ = h < history_size |
| + ? ev.GetHistoricalEventTime(h) |
| + : ev.GetEventTime(); |
| + accept = false; |
| + } |
| + } |
| + } |
| + sample_count += pointersample_count; |
| + } |
| + |
| + const float avg = total / sample_count; |
| + |
| + if (accept) { |
| + float newAccepted = (touch_upper_ + touch_lower_ + avg) / 3; |
|
Xianzhu
2014/02/19 22:01:56
s/newAccepted/new_accepted/
jdduke (slow)
2014/02/20 18:01:09
Done.
|
| + touch_upper_ = (touch_upper_ + newAccepted) / 2; |
| + touch_lower_ = (touch_lower_ + newAccepted) / 2; |
| + touch_history_last_accepted_ = newAccepted; |
| + touch_history_direction_ = 0; |
| + touch_history_last_accepted_time_ = ev.GetEventTime(); |
| + } |
| +} |
| + |
| +void ScaleGestureDetector::ClearTouchHistory() { |
| + touch_upper_ = std::numeric_limits<float>::quiet_NaN(); |
| + touch_lower_ = std::numeric_limits<float>::quiet_NaN(); |
| + touch_history_last_accepted_ = std::numeric_limits<float>::quiet_NaN(); |
| + touch_history_direction_ = 0; |
| + touch_history_last_accepted_time_ = base::TimeTicks(); |
| +} |
| + |
| +bool ScaleGestureDetector::InDoubleTapMode() const { |
| + return double_tap_mode_ == DOUBLE_TAP_MODE_IN_PROGRESS; |
| +} |
| + |
| +} // namespace ui |