| // 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 <algorithm> |
| #include <cmath> |
| |
| #include "base/logging.h" |
| #include "ui/events/gesture_detection/motion_event.h" |
| #include "ui/events/gesture_detection/scale_gesture_listeners.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 float kScaleFactor = .5f; |
| |
| } // namespace |
| |
| // Note: These constants were taken directly from the default (unscaled) |
| // versions found in Android's ViewConfiguration. Do not change these default |
| // values without explicitly consulting an OWNER. |
| ScaleGestureDetector::Config::Config() |
| : span_slop(16), |
| min_scaling_span(200), |
| min_pinch_update_span_delta(0), |
| stylus_scale_enabled(false) {} |
| |
| ScaleGestureDetector::Config::~Config() {} |
| |
| ScaleGestureDetector::ScaleGestureDetector(const Config& config, |
| ScaleGestureListener* listener) |
| : listener_(listener), |
| stylus_scale_enabled_(config.stylus_scale_enabled), |
| focus_x_(0), |
| focus_y_(0), |
| 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), |
| anchored_scale_start_x_(0), |
| anchored_scale_start_y_(0), |
| anchored_scale_mode_(ANCHORED_SCALE_MODE_NONE), |
| event_before_or_above_starting_gesture_event_(false) { |
| DCHECK(listener_); |
| span_slop_ = config.span_slop + kSlopEpsilon; |
| min_span_ = config.min_scaling_span + kSlopEpsilon; |
| } |
| |
| ScaleGestureDetector::~ScaleGestureDetector() {} |
| |
| bool ScaleGestureDetector::OnTouchEvent(const MotionEvent& event) { |
| curr_time_ = event.GetEventTime(); |
| |
| const int action = event.GetAction(); |
| |
| const int count = static_cast<int>(event.GetPointerCount()); |
| const bool is_stylus_button_down = |
| (event.GetButtonState() & MotionEvent::BUTTON_STYLUS_PRIMARY) != 0; |
| |
| const bool anchored_scale_cancelled = |
| anchored_scale_mode_ == ANCHORED_SCALE_MODE_STYLUS && |
| !is_stylus_button_down; |
| |
| const bool stream_complete = |
| action == MotionEvent::ACTION_UP || |
| action == MotionEvent::ACTION_CANCEL || anchored_scale_cancelled || |
| (action == MotionEvent::ACTION_POINTER_DOWN && InAnchoredScaleMode()); |
| |
| 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 (InAnchoredScaleMode() && stream_complete) { |
| ResetScaleWithSpan(0); |
| } |
| |
| if (stream_complete) |
| return true; |
| } |
| |
| if (!in_progress_ && stylus_scale_enabled_ && !InAnchoredScaleMode() && |
| !stream_complete && is_stylus_button_down) { |
| // Start of a stylus scale gesture. |
| anchored_scale_start_x_ = event.GetX(); |
| anchored_scale_start_y_ = event.GetY(); |
| anchored_scale_mode_ = ANCHORED_SCALE_MODE_STYLUS; |
| initial_span_ = 0; |
| } |
| |
| const bool config_changed = action == MotionEvent::ACTION_DOWN || |
| action == MotionEvent::ACTION_POINTER_UP || |
| action == MotionEvent::ACTION_POINTER_DOWN || |
| anchored_scale_cancelled; |
| |
| 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 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 (InAnchoredScaleMode()) { |
| // In double tap mode, the focal pt is always where the double tap |
| // gesture started. |
| focus_x = anchored_scale_start_x_; |
| focus_y = anchored_scale_start_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; |
| } |
| |
| // 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); |
| } |
| |
| const float dev_x = dev_sum_x * inverse_unreleased_point_count; |
| const float dev_y = dev_sum_y * inverse_unreleased_point_count; |
| |
| // 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 (InAnchoredScaleMode()) { |
| 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 (!InAnchoredScaleMode() && 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 = InAnchoredScaleMode() ? 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, 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; |
| } |
| |
| bool ScaleGestureDetector::IsInProgress() const { return in_progress_; } |
| |
| bool ScaleGestureDetector::InAnchoredScaleMode() const { |
| return anchored_scale_mode_ != ANCHORED_SCALE_MODE_NONE; |
| } |
| |
| 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 (InAnchoredScaleMode()) { |
| // 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. |
| anchored_scale_start_x_ = ev.GetX(); |
| anchored_scale_start_y_ = ev.GetY(); |
| anchored_scale_mode_ = ANCHORED_SCALE_MODE_DOUBLE_TAP; |
| return true; |
| } |
| |
| void ScaleGestureDetector::ResetScaleWithSpan(float span) { |
| in_progress_ = false; |
| initial_span_ = span; |
| anchored_scale_mode_ = ANCHORED_SCALE_MODE_NONE; |
| } |
| |
| } // namespace ui |