| Index: content/browser/renderer_host/input/gestures/scale_gesture_detector.cc
|
| diff --git a/content/browser/renderer_host/input/gestures/scale_gesture_detector.cc b/content/browser/renderer_host/input/gestures/scale_gesture_detector.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cd8a6bfaf1b782cafe4eeda1c096c90da44505ce
|
| --- /dev/null
|
| +++ b/content/browser/renderer_host/input/gestures/scale_gesture_detector.cc
|
| @@ -0,0 +1,381 @@
|
| +// 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 "content/browser/renderer_host/input/gestures/scale_gesture_detector.h"
|
| +
|
| +#include <limits.h>
|
| +#include <math.h>
|
| +
|
| +#include "base/logging.h"
|
| +#include "content/browser/renderer_host/input/gestures/motion_event.h"
|
| +
|
| +using base::TimeDelta;
|
| +using base::TimeTicks;
|
| +
|
| +namespace content {
|
| +namespace {
|
| +const TimeDelta TOUCH_STABILIZE_TIME = TimeDelta::FromMilliseconds(128);
|
| +const float SCALE_FACTOR = .5f;
|
| +}
|
| +
|
| +ScaleGestureDetector::Config::Config()
|
| + : quick_scale_enabled(false),
|
| + min_scaling_touch_major(1),
|
| + min_scaling_span(1) {}
|
| +
|
| +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(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.GetActionMasked();
|
| +
|
| + // 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 pionter_up = action == MotionEvent::ACTION_POINTER_UP;
|
| + const int skip_index = pionter_up ? event.GetActionIndex() : -1;
|
| +
|
| + // Determine focal point
|
| + float sum_x = 0, sum_y = 0;
|
| + const int count = event.GetPointerCount();
|
| + const int div = pionter_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 majorDelta = major - touch_history_last_accepted_;
|
| + const int direction_sig =
|
| + majorDelta > 0 ? 1 : (majorDelta < 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;
|
| + 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 content
|
|
|