Home | History | Annotate | Download | only in gesture_detection
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ui/events/gesture_detection/scale_gesture_detector.h"
      6 
      7 #include <limits.h>
      8 #include <cmath>
      9 
     10 #include "base/float_util.h"
     11 #include "base/logging.h"
     12 #include "ui/events/gesture_detection/motion_event.h"
     13 
     14 using base::TimeDelta;
     15 using base::TimeTicks;
     16 
     17 namespace ui {
     18 namespace {
     19 
     20 // Using a small epsilon when comparing slop distances allows pixel perfect
     21 // slop determination when using fractional DPI coordinates (assuming the slop
     22 // region and DPI scale are reasonably proportioned).
     23 const float kSlopEpsilon = .05f;
     24 
     25 const int kTouchStabilizeTimeMs = 128;
     26 
     27 const float kScaleFactor = .5f;
     28 
     29 }  // namespace
     30 
     31 // Note: These constants were taken directly from the default (unscaled)
     32 // versions found in Android's ViewConfiguration.
     33 ScaleGestureDetector::Config::Config()
     34     : min_scaling_touch_major(48),
     35       min_scaling_span(200),
     36       quick_scale_enabled(true),
     37       min_pinch_update_span_delta(0) {
     38 }
     39 
     40 ScaleGestureDetector::Config::~Config() {}
     41 
     42 bool ScaleGestureDetector::SimpleScaleGestureListener::OnScale(
     43     const ScaleGestureDetector&, const MotionEvent&) {
     44   return false;
     45 }
     46 
     47 bool ScaleGestureDetector::SimpleScaleGestureListener::OnScaleBegin(
     48     const ScaleGestureDetector&, const MotionEvent&) {
     49   return true;
     50 }
     51 
     52 void ScaleGestureDetector::SimpleScaleGestureListener::OnScaleEnd(
     53     const ScaleGestureDetector&, const MotionEvent&) {}
     54 
     55 ScaleGestureDetector::ScaleGestureDetector(const Config& config,
     56                                            ScaleGestureListener* listener)
     57     : listener_(listener),
     58       config_(config),
     59       focus_x_(0),
     60       focus_y_(0),
     61       quick_scale_enabled_(false),
     62       curr_span_(0),
     63       prev_span_(0),
     64       initial_span_(0),
     65       curr_span_x_(0),
     66       curr_span_y_(0),
     67       prev_span_x_(0),
     68       prev_span_y_(0),
     69       in_progress_(0),
     70       span_slop_(0),
     71       min_span_(0),
     72       touch_upper_(0),
     73       touch_lower_(0),
     74       touch_history_last_accepted_(0),
     75       touch_history_direction_(0),
     76       touch_min_major_(0),
     77       double_tap_focus_x_(0),
     78       double_tap_focus_y_(0),
     79       double_tap_mode_(DOUBLE_TAP_MODE_NONE),
     80       event_before_or_above_starting_gesture_event_(false) {
     81   DCHECK(listener_);
     82   span_slop_ =
     83       (config.gesture_detector_config.touch_slop + kSlopEpsilon) * 2;
     84   touch_min_major_ = config.min_scaling_touch_major;
     85   min_span_ = config.min_scaling_span + kSlopEpsilon;
     86   ResetTouchHistory();
     87   SetQuickScaleEnabled(config.quick_scale_enabled);
     88 }
     89 
     90 ScaleGestureDetector::~ScaleGestureDetector() {}
     91 
     92 bool ScaleGestureDetector::OnTouchEvent(const MotionEvent& event) {
     93   curr_time_ = event.GetEventTime();
     94 
     95   const int action = event.GetAction();
     96 
     97   // Forward the event to check for double tap gesture.
     98   if (quick_scale_enabled_) {
     99     DCHECK(gesture_detector_);
    100     gesture_detector_->OnTouchEvent(event);
    101   }
    102 
    103   const bool stream_complete =
    104       action == MotionEvent::ACTION_UP ||
    105       action == MotionEvent::ACTION_CANCEL ||
    106       (action == MotionEvent::ACTION_POINTER_DOWN && InDoubleTapMode());
    107 
    108   if (action == MotionEvent::ACTION_DOWN || stream_complete) {
    109     // Reset any scale in progress with the listener.
    110     // If it's an ACTION_DOWN we're beginning a new event stream.
    111     // This means the app probably didn't give us all the events. Shame on it.
    112     if (in_progress_) {
    113       listener_->OnScaleEnd(*this, event);
    114       ResetScaleWithSpan(0);
    115     } else if (InDoubleTapMode() && stream_complete) {
    116       ResetScaleWithSpan(0);
    117     }
    118 
    119     if (stream_complete) {
    120       ResetTouchHistory();
    121       return true;
    122     }
    123   }
    124 
    125   const bool config_changed = action == MotionEvent::ACTION_DOWN ||
    126                               action == MotionEvent::ACTION_POINTER_UP ||
    127                               action == MotionEvent::ACTION_POINTER_DOWN;
    128 
    129   const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
    130   const int skip_index = pointer_up ? event.GetActionIndex() : -1;
    131 
    132   // Determine focal point.
    133   float sum_x = 0, sum_y = 0;
    134   const int count = static_cast<int>(event.GetPointerCount());
    135   const int unreleased_point_count = pointer_up ? count - 1 : count;
    136   const float inverse_unreleased_point_count = 1.0f / unreleased_point_count;
    137 
    138   float focus_x;
    139   float focus_y;
    140   if (InDoubleTapMode()) {
    141     // In double tap mode, the focal pt is always where the double tap
    142     // gesture started.
    143     focus_x = double_tap_focus_x_;
    144     focus_y = double_tap_focus_y_;
    145     if (event.GetY() < focus_y) {
    146       event_before_or_above_starting_gesture_event_ = true;
    147     } else {
    148       event_before_or_above_starting_gesture_event_ = false;
    149     }
    150   } else {
    151     for (int i = 0; i < count; i++) {
    152       if (skip_index == i)
    153         continue;
    154       sum_x += event.GetX(i);
    155       sum_y += event.GetY(i);
    156     }
    157 
    158     focus_x = sum_x * inverse_unreleased_point_count;
    159     focus_y = sum_y * inverse_unreleased_point_count;
    160   }
    161 
    162   AddTouchHistory(event);
    163 
    164   // Determine average deviation from focal point.
    165   float dev_sum_x = 0, dev_sum_y = 0;
    166   for (int i = 0; i < count; i++) {
    167     if (skip_index == i)
    168       continue;
    169 
    170     dev_sum_x += std::abs(event.GetX(i) - focus_x);
    171     dev_sum_y += std::abs(event.GetY(i) - focus_y);
    172   }
    173   // Convert the resulting diameter into a radius, to include touch
    174   // radius in overall deviation.
    175   const float touch_size = touch_history_last_accepted_ / 2;
    176 
    177   const float dev_x = (dev_sum_x * inverse_unreleased_point_count) + touch_size;
    178   const float dev_y = (dev_sum_y * inverse_unreleased_point_count) + touch_size;
    179 
    180   // Span is the average distance between touch points through the focal point;
    181   // i.e. the diameter of the circle with a radius of the average deviation from
    182   // the focal point.
    183   const float span_x = dev_x * 2;
    184   const float span_y = dev_y * 2;
    185   float span;
    186   if (InDoubleTapMode()) {
    187     span = span_y;
    188   } else {
    189     span = std::sqrt(span_x * span_x + span_y * span_y);
    190   }
    191 
    192   // Dispatch begin/end events as needed.
    193   // If the configuration changes, notify the app to reset its current state by
    194   // beginning a fresh scale event stream.
    195   const bool was_in_progress = in_progress_;
    196   focus_x_ = focus_x;
    197   focus_y_ = focus_y;
    198   if (!InDoubleTapMode() && in_progress_ &&
    199       (span < min_span_ || config_changed)) {
    200     listener_->OnScaleEnd(*this, event);
    201     ResetScaleWithSpan(span);
    202   }
    203   if (config_changed) {
    204     prev_span_x_ = curr_span_x_ = span_x;
    205     prev_span_y_ = curr_span_y_ = span_y;
    206     initial_span_ = prev_span_ = curr_span_ = span;
    207   }
    208 
    209   const float min_span = InDoubleTapMode() ? span_slop_ : min_span_;
    210   if (!in_progress_ && span >= min_span && (InDoubleTapMode() || count > 1) &&
    211       (was_in_progress || std::abs(span - initial_span_) > span_slop_)) {
    212     prev_span_x_ = curr_span_x_ = span_x;
    213     prev_span_y_ = curr_span_y_ = span_y;
    214     prev_span_ = curr_span_ = span;
    215     prev_time_ = curr_time_;
    216     in_progress_ = listener_->OnScaleBegin(*this, event);
    217   }
    218 
    219   // Handle motion; focal point and span/scale factor are changing.
    220   if (action == MotionEvent::ACTION_MOVE) {
    221     curr_span_x_ = span_x;
    222     curr_span_y_ = span_y;
    223     curr_span_ = span;
    224 
    225     bool update_prev = true;
    226 
    227     if (in_progress_) {
    228       update_prev = listener_->OnScale(*this, event);
    229     }
    230 
    231     if (update_prev) {
    232       prev_span_x_ = curr_span_x_;
    233       prev_span_y_ = curr_span_y_;
    234       prev_span_ = curr_span_;
    235       prev_time_ = curr_time_;
    236     }
    237   }
    238 
    239   return true;
    240 }
    241 
    242 void ScaleGestureDetector::SetQuickScaleEnabled(bool scales) {
    243   quick_scale_enabled_ = scales;
    244   if (quick_scale_enabled_ && !gesture_detector_) {
    245     gesture_detector_.reset(
    246         new GestureDetector(config_.gesture_detector_config, this, this));
    247   }
    248 }
    249 
    250 bool ScaleGestureDetector::IsQuickScaleEnabled() const {
    251   return quick_scale_enabled_;
    252 }
    253 
    254 bool ScaleGestureDetector::IsInProgress() const { return in_progress_; }
    255 
    256 bool ScaleGestureDetector::InDoubleTapMode() const {
    257   return double_tap_mode_ == DOUBLE_TAP_MODE_IN_PROGRESS;
    258 }
    259 
    260 float ScaleGestureDetector::GetFocusX() const { return focus_x_; }
    261 
    262 float ScaleGestureDetector::GetFocusY() const { return focus_y_; }
    263 
    264 float ScaleGestureDetector::GetCurrentSpan() const { return curr_span_; }
    265 
    266 float ScaleGestureDetector::GetCurrentSpanX() const { return curr_span_x_; }
    267 
    268 float ScaleGestureDetector::GetCurrentSpanY() const { return curr_span_y_; }
    269 
    270 float ScaleGestureDetector::GetPreviousSpan() const { return prev_span_; }
    271 
    272 float ScaleGestureDetector::GetPreviousSpanX() const { return prev_span_x_; }
    273 
    274 float ScaleGestureDetector::GetPreviousSpanY() const { return prev_span_y_; }
    275 
    276 float ScaleGestureDetector::GetScaleFactor() const {
    277   if (InDoubleTapMode()) {
    278     // Drag is moving up; the further away from the gesture start, the smaller
    279     // the span should be, the closer, the larger the span, and therefore the
    280     // larger the scale.
    281     const bool scale_up = (event_before_or_above_starting_gesture_event_ &&
    282                            (curr_span_ < prev_span_)) ||
    283                           (!event_before_or_above_starting_gesture_event_ &&
    284                            (curr_span_ > prev_span_));
    285     const float span_diff =
    286         (std::abs(1.f - (curr_span_ / prev_span_)) * kScaleFactor);
    287     return prev_span_ <= 0 ? 1.f
    288                            : (scale_up ? (1.f + span_diff) : (1.f - span_diff));
    289   }
    290   return prev_span_ > 0 ? curr_span_ / prev_span_ : 1;
    291 }
    292 
    293 base::TimeDelta ScaleGestureDetector::GetTimeDelta() const {
    294   return curr_time_ - prev_time_;
    295 }
    296 
    297 base::TimeTicks ScaleGestureDetector::GetEventTime() const {
    298   return curr_time_;
    299 }
    300 
    301 bool ScaleGestureDetector::OnDoubleTap(const MotionEvent& ev) {
    302   // Double tap: start watching for a swipe.
    303   double_tap_focus_x_ = ev.GetX();
    304   double_tap_focus_y_ = ev.GetY();
    305   double_tap_mode_ = DOUBLE_TAP_MODE_IN_PROGRESS;
    306   return true;
    307 }
    308 
    309 void ScaleGestureDetector::AddTouchHistory(const MotionEvent& ev) {
    310   const base::TimeTicks current_time = ev.GetEventTime();
    311   DCHECK(!current_time.is_null());
    312   const int count = static_cast<int>(ev.GetPointerCount());
    313   bool accept = touch_history_last_accepted_time_.is_null() ||
    314                 (current_time - touch_history_last_accepted_time_) >=
    315                     base::TimeDelta::FromMilliseconds(kTouchStabilizeTimeMs);
    316   float total = 0;
    317   int sample_count = 0;
    318   for (int i = 0; i < count; i++) {
    319     const bool has_last_accepted = !base::IsNaN(touch_history_last_accepted_);
    320     const int history_size = static_cast<int>(ev.GetHistorySize());
    321     const int pointersample_count = history_size + 1;
    322     for (int h = 0; h < pointersample_count; h++) {
    323       float major;
    324       if (h < history_size) {
    325         major = ev.GetHistoricalTouchMajor(i, h);
    326       } else {
    327         major = ev.GetTouchMajor(i);
    328       }
    329       if (major < touch_min_major_)
    330         major = touch_min_major_;
    331       total += major;
    332 
    333       if (base::IsNaN(touch_upper_) || major > touch_upper_) {
    334         touch_upper_ = major;
    335       }
    336       if (base::IsNaN(touch_lower_) || major < touch_lower_) {
    337         touch_lower_ = major;
    338       }
    339 
    340       if (has_last_accepted) {
    341         const float major_delta = major - touch_history_last_accepted_;
    342         const int direction_sig =
    343             major_delta > 0 ? 1 : (major_delta < 0 ? -1 : 0);
    344         if (direction_sig != touch_history_direction_ ||
    345             (direction_sig == 0 && touch_history_direction_ == 0)) {
    346           touch_history_direction_ = direction_sig;
    347           touch_history_last_accepted_time_ = h < history_size
    348                                                   ? ev.GetHistoricalEventTime(h)
    349                                                   : ev.GetEventTime();
    350           accept = false;
    351         }
    352       }
    353     }
    354     sample_count += pointersample_count;
    355   }
    356 
    357   const float avg = total / sample_count;
    358 
    359   if (accept) {
    360     float new_accepted = (touch_upper_ + touch_lower_ + avg) / 3;
    361     touch_upper_ = (touch_upper_ + new_accepted) / 2;
    362     touch_lower_ = (touch_lower_ + new_accepted) / 2;
    363     touch_history_last_accepted_ = new_accepted;
    364     touch_history_direction_ = 0;
    365     touch_history_last_accepted_time_ = ev.GetEventTime();
    366   }
    367 }
    368 
    369 void ScaleGestureDetector::ResetTouchHistory() {
    370   touch_upper_ = std::numeric_limits<float>::quiet_NaN();
    371   touch_lower_ = std::numeric_limits<float>::quiet_NaN();
    372   touch_history_last_accepted_ = std::numeric_limits<float>::quiet_NaN();
    373   touch_history_direction_ = 0;
    374   touch_history_last_accepted_time_ = base::TimeTicks();
    375 }
    376 
    377 void ScaleGestureDetector::ResetScaleWithSpan(float span) {
    378   in_progress_ = false;
    379   initial_span_ = span;
    380   double_tap_mode_ = DOUBLE_TAP_MODE_NONE;
    381 }
    382 
    383 }  // namespace ui
    384