root/ui/events/gesture_detection/scale_gesture_detector.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. min_scaling_span
  2. OnScale
  3. OnScaleBegin
  4. OnScaleEnd
  5. event_before_or_above_starting_gesture_event_
  6. OnTouchEvent
  7. SetQuickScaleEnabled
  8. IsQuickScaleEnabled
  9. IsInProgress
  10. InDoubleTapMode
  11. GetFocusX
  12. GetFocusY
  13. GetCurrentSpan
  14. GetCurrentSpanX
  15. GetCurrentSpanY
  16. GetPreviousSpan
  17. GetPreviousSpanX
  18. GetPreviousSpanY
  19. GetScaleFactor
  20. GetTimeDelta
  21. GetEventTime
  22. OnDoubleTap
  23. AddTouchHistory
  24. ClearTouchHistory

// 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 {

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()
    : quick_scale_enabled(true),
      min_scaling_touch_major(48),
      min_scaling_span(200) {}

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.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, event);
      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 = static_cast<int>(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, event);
    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, 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 = base::TimeTicks::Now();
  const int count = static_cast<int>(ev.GetPointerCount());
  bool accept = (current_time - touch_history_last_accepted_time_)
                    .InMilliseconds() >= 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::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();
}

}  // namespace ui

/* [<][>][^][v][top][bottom][index][help] */