summaryrefslogtreecommitdiffstats
path: root/chromium/ui/events/gesture_detection/scale_gesture_detector.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ui/events/gesture_detection/scale_gesture_detector.cc')
-rw-r--r--chromium/ui/events/gesture_detection/scale_gesture_detector.cc383
1 files changed, 383 insertions, 0 deletions
diff --git a/chromium/ui/events/gesture_detection/scale_gesture_detector.cc b/chromium/ui/events/gesture_detection/scale_gesture_detector.cc
new file mode 100644
index 00000000000..18fd6d85b59
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/scale_gesture_detector.cc
@@ -0,0 +1,383 @@
+// 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 <cmath>
+
+#include "base/float_util.h"
+#include "base/logging.h"
+#include "ui/events/gesture_detection/motion_event.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace ui {
+namespace {
+
+// Using a small epsilon when comparing slop distances allows pixel perfect
+// slop determination when using fractional DPI coordinates (assuming the slop
+// region and DPI scale are reasonably proportioned).
+const float kSlopEpsilon = .05f;
+
+const int kTouchStabilizeTimeMs = 128;
+
+const float kScaleFactor = .5f;
+
+} // namespace
+
+// Note: These constants were taken directly from the default (unscaled)
+// versions found in Android's ViewConfiguration.
+ScaleGestureDetector::Config::Config()
+ : min_scaling_touch_major(48),
+ min_scaling_span(200),
+ quick_scale_enabled(true),
+ min_pinch_update_span_delta(0) {
+}
+
+ScaleGestureDetector::Config::~Config() {}
+
+bool ScaleGestureDetector::SimpleScaleGestureListener::OnScale(
+ const ScaleGestureDetector&, const MotionEvent&) {
+ return false;
+}
+
+bool ScaleGestureDetector::SimpleScaleGestureListener::OnScaleBegin(
+ const ScaleGestureDetector&, const MotionEvent&) {
+ return true;
+}
+
+void ScaleGestureDetector::SimpleScaleGestureListener::OnScaleEnd(
+ const ScaleGestureDetector&, const MotionEvent&) {}
+
+ScaleGestureDetector::ScaleGestureDetector(const Config& config,
+ ScaleGestureListener* 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.touch_slop + kSlopEpsilon) * 2;
+ touch_min_major_ = config.min_scaling_touch_major;
+ min_span_ = config.min_scaling_span + kSlopEpsilon;
+ ResetTouchHistory();
+ 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 ||
+ (action == MotionEvent::ACTION_POINTER_DOWN && InDoubleTapMode());
+
+ 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, event);
+ ResetScaleWithSpan(0);
+ } else if (InDoubleTapMode() && stream_complete) {
+ ResetScaleWithSpan(0);
+ }
+
+ if (stream_complete) {
+ ResetTouchHistory();
+ 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 = static_cast<int>(event.GetPointerCount());
+ const int unreleased_point_count = pointer_up ? count - 1 : count;
+ const float inverse_unreleased_point_count = 1.0f / unreleased_point_count;
+
+ float focus_x;
+ float focus_y;
+ if (InDoubleTapMode()) {
+ // 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 * inverse_unreleased_point_count;
+ focus_y = sum_y * inverse_unreleased_point_count;
+ }
+
+ 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;
+
+ dev_sum_x += std::abs(event.GetX(i) - focus_x);
+ dev_sum_y += std::abs(event.GetY(i) - focus_y);
+ }
+ // Convert the resulting diameter into a radius, to include touch
+ // radius in overall deviation.
+ const float touch_size = touch_history_last_accepted_ / 2;
+
+ const float dev_x = (dev_sum_x * inverse_unreleased_point_count) + touch_size;
+ const float dev_y = (dev_sum_y * inverse_unreleased_point_count) + touch_size;
+
+ // 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, event);
+ ResetScaleWithSpan(span);
+ }
+ 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 float min_span = InDoubleTapMode() ? span_slop_ : min_span_;
+ if (!in_progress_ && span >= min_span && (InDoubleTapMode() || count > 1) &&
+ (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, event);
+ }
+
+ // 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, event);
+ }
+
+ 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_; }
+
+bool ScaleGestureDetector::InDoubleTapMode() const {
+ return double_tap_mode_ == DOUBLE_TAP_MODE_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_)) * kScaleFactor);
+ 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 = ev.GetEventTime();
+ DCHECK(!current_time.is_null());
+ const int count = static_cast<int>(ev.GetPointerCount());
+ bool accept = touch_history_last_accepted_time_.is_null() ||
+ (current_time - touch_history_last_accepted_time_) >=
+ base::TimeDelta::FromMilliseconds(kTouchStabilizeTimeMs);
+ float total = 0;
+ int sample_count = 0;
+ for (int i = 0; i < count; i++) {
+ const bool has_last_accepted = !base::IsNaN(touch_history_last_accepted_);
+ const int history_size = static_cast<int>(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 (base::IsNaN(touch_upper_) || major > touch_upper_) {
+ touch_upper_ = major;
+ }
+ if (base::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 new_accepted = (touch_upper_ + touch_lower_ + avg) / 3;
+ touch_upper_ = (touch_upper_ + new_accepted) / 2;
+ touch_lower_ = (touch_lower_ + new_accepted) / 2;
+ touch_history_last_accepted_ = new_accepted;
+ touch_history_direction_ = 0;
+ touch_history_last_accepted_time_ = ev.GetEventTime();
+ }
+}
+
+void ScaleGestureDetector::ResetTouchHistory() {
+ 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();
+}
+
+void ScaleGestureDetector::ResetScaleWithSpan(float span) {
+ in_progress_ = false;
+ initial_span_ = span;
+ double_tap_mode_ = DOUBLE_TAP_MODE_NONE;
+}
+
+} // namespace ui