DCHECK if shader compilation fails that it's due to context loss.
[chromium-blink-merge.git] / ui / events / gesture_detection / scale_gesture_detector.cc
blobfb27cd9458e11829cb12669c70a6ddae165f1d4a
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.
5 #include "ui/events/gesture_detection/scale_gesture_detector.h"
7 #include <limits.h>
9 #include <cmath>
11 #include "base/logging.h"
12 #include "ui/events/gesture_detection/motion_event.h"
13 #include "ui/events/gesture_detection/scale_gesture_listeners.h"
15 using base::TimeDelta;
16 using base::TimeTicks;
18 namespace ui {
19 namespace {
21 // Using a small epsilon when comparing slop distances allows pixel perfect
22 // slop determination when using fractional DPI coordinates (assuming the slop
23 // region and DPI scale are reasonably proportioned).
24 const float kSlopEpsilon = .05f;
26 const int kTouchStabilizeTimeMs = 128;
28 const float kScaleFactor = .5f;
30 } // namespace
32 // Note: These constants were taken directly from the default (unscaled)
33 // versions found in Android's ViewConfiguration. Do not change these default
34 // values without explicitly consulting an OWNER.
35 ScaleGestureDetector::Config::Config()
36 : span_slop(16),
37 min_scaling_touch_major(48),
38 min_scaling_span(200),
39 min_pinch_update_span_delta(0) {
42 ScaleGestureDetector::Config::~Config() {}
44 ScaleGestureDetector::ScaleGestureDetector(const Config& config,
45 ScaleGestureListener* listener)
46 : listener_(listener),
47 focus_x_(0),
48 focus_y_(0),
49 curr_span_(0),
50 prev_span_(0),
51 initial_span_(0),
52 curr_span_x_(0),
53 curr_span_y_(0),
54 prev_span_x_(0),
55 prev_span_y_(0),
56 in_progress_(0),
57 span_slop_(0),
58 min_span_(0),
59 touch_upper_(0),
60 touch_lower_(0),
61 touch_history_last_accepted_(0),
62 touch_history_direction_(0),
63 touch_min_major_(0),
64 touch_max_major_(0),
65 double_tap_focus_x_(0),
66 double_tap_focus_y_(0),
67 double_tap_mode_(DOUBLE_TAP_MODE_NONE),
68 event_before_or_above_starting_gesture_event_(false) {
69 DCHECK(listener_);
70 span_slop_ = config.span_slop + kSlopEpsilon;
71 touch_min_major_ = config.min_scaling_touch_major;
72 touch_max_major_ = std::min(config.min_scaling_span / std::sqrt(2.f),
73 2.f * touch_min_major_);
74 min_span_ = config.min_scaling_span + kSlopEpsilon;
75 ResetTouchHistory();
78 ScaleGestureDetector::~ScaleGestureDetector() {}
80 bool ScaleGestureDetector::OnTouchEvent(const MotionEvent& event) {
81 curr_time_ = event.GetEventTime();
83 const int action = event.GetAction();
85 const bool stream_complete =
86 action == MotionEvent::ACTION_UP ||
87 action == MotionEvent::ACTION_CANCEL ||
88 (action == MotionEvent::ACTION_POINTER_DOWN && InDoubleTapMode());
90 if (action == MotionEvent::ACTION_DOWN || stream_complete) {
91 // Reset any scale in progress with the listener.
92 // If it's an ACTION_DOWN we're beginning a new event stream.
93 // This means the app probably didn't give us all the events. Shame on it.
94 if (in_progress_) {
95 listener_->OnScaleEnd(*this, event);
96 ResetScaleWithSpan(0);
97 } else if (InDoubleTapMode() && stream_complete) {
98 ResetScaleWithSpan(0);
101 if (stream_complete) {
102 ResetTouchHistory();
103 return true;
107 const bool config_changed = action == MotionEvent::ACTION_DOWN ||
108 action == MotionEvent::ACTION_POINTER_UP ||
109 action == MotionEvent::ACTION_POINTER_DOWN;
111 const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
112 const int skip_index = pointer_up ? event.GetActionIndex() : -1;
114 // Determine focal point.
115 float sum_x = 0, sum_y = 0;
116 const int count = static_cast<int>(event.GetPointerCount());
117 const int unreleased_point_count = pointer_up ? count - 1 : count;
118 const float inverse_unreleased_point_count = 1.0f / unreleased_point_count;
120 float focus_x;
121 float focus_y;
122 if (InDoubleTapMode()) {
123 // In double tap mode, the focal pt is always where the double tap
124 // gesture started.
125 focus_x = double_tap_focus_x_;
126 focus_y = double_tap_focus_y_;
127 if (event.GetY() < focus_y) {
128 event_before_or_above_starting_gesture_event_ = true;
129 } else {
130 event_before_or_above_starting_gesture_event_ = false;
132 } else {
133 for (int i = 0; i < count; i++) {
134 if (skip_index == i)
135 continue;
136 sum_x += event.GetX(i);
137 sum_y += event.GetY(i);
140 focus_x = sum_x * inverse_unreleased_point_count;
141 focus_y = sum_y * inverse_unreleased_point_count;
144 AddTouchHistory(event);
146 // Determine average deviation from focal point.
147 float dev_sum_x = 0, dev_sum_y = 0;
148 for (int i = 0; i < count; i++) {
149 if (skip_index == i)
150 continue;
152 dev_sum_x += std::abs(event.GetX(i) - focus_x);
153 dev_sum_y += std::abs(event.GetY(i) - focus_y);
155 // Convert the resulting diameter into a radius, to include touch
156 // radius in overall deviation.
157 const float touch_radius = touch_history_last_accepted_ / 2;
159 const float dev_x = dev_sum_x * inverse_unreleased_point_count + touch_radius;
160 const float dev_y = dev_sum_y * inverse_unreleased_point_count + touch_radius;
162 // Span is the average distance between touch points through the focal point;
163 // i.e. the diameter of the circle with a radius of the average deviation from
164 // the focal point.
165 const float span_x = dev_x * 2;
166 const float span_y = dev_y * 2;
167 float span;
168 if (InDoubleTapMode()) {
169 span = span_y;
170 } else {
171 span = std::sqrt(span_x * span_x + span_y * span_y);
174 // Dispatch begin/end events as needed.
175 // If the configuration changes, notify the app to reset its current state by
176 // beginning a fresh scale event stream.
177 const bool was_in_progress = in_progress_;
178 focus_x_ = focus_x;
179 focus_y_ = focus_y;
180 if (!InDoubleTapMode() && in_progress_ &&
181 (span < min_span_ || config_changed)) {
182 listener_->OnScaleEnd(*this, event);
183 ResetScaleWithSpan(span);
185 if (config_changed) {
186 prev_span_x_ = curr_span_x_ = span_x;
187 prev_span_y_ = curr_span_y_ = span_y;
188 initial_span_ = prev_span_ = curr_span_ = span;
191 const float min_span = InDoubleTapMode() ? span_slop_ : min_span_;
192 if (!in_progress_ && span >= min_span &&
193 (was_in_progress || std::abs(span - initial_span_) > span_slop_)) {
194 prev_span_x_ = curr_span_x_ = span_x;
195 prev_span_y_ = curr_span_y_ = span_y;
196 prev_span_ = curr_span_ = span;
197 prev_time_ = curr_time_;
198 in_progress_ = listener_->OnScaleBegin(*this, event);
201 // Handle motion; focal point and span/scale factor are changing.
202 if (action == MotionEvent::ACTION_MOVE) {
203 curr_span_x_ = span_x;
204 curr_span_y_ = span_y;
205 curr_span_ = span;
207 bool update_prev = true;
209 if (in_progress_) {
210 update_prev = listener_->OnScale(*this, event);
213 if (update_prev) {
214 prev_span_x_ = curr_span_x_;
215 prev_span_y_ = curr_span_y_;
216 prev_span_ = curr_span_;
217 prev_time_ = curr_time_;
221 return true;
224 bool ScaleGestureDetector::IsInProgress() const { return in_progress_; }
226 bool ScaleGestureDetector::InDoubleTapMode() const {
227 return double_tap_mode_ == DOUBLE_TAP_MODE_IN_PROGRESS;
230 float ScaleGestureDetector::GetFocusX() const { return focus_x_; }
232 float ScaleGestureDetector::GetFocusY() const { return focus_y_; }
234 float ScaleGestureDetector::GetCurrentSpan() const { return curr_span_; }
236 float ScaleGestureDetector::GetCurrentSpanX() const { return curr_span_x_; }
238 float ScaleGestureDetector::GetCurrentSpanY() const { return curr_span_y_; }
240 float ScaleGestureDetector::GetPreviousSpan() const { return prev_span_; }
242 float ScaleGestureDetector::GetPreviousSpanX() const { return prev_span_x_; }
244 float ScaleGestureDetector::GetPreviousSpanY() const { return prev_span_y_; }
246 float ScaleGestureDetector::GetScaleFactor() const {
247 if (InDoubleTapMode()) {
248 // Drag is moving up; the further away from the gesture start, the smaller
249 // the span should be, the closer, the larger the span, and therefore the
250 // larger the scale.
251 const bool scale_up = (event_before_or_above_starting_gesture_event_ &&
252 (curr_span_ < prev_span_)) ||
253 (!event_before_or_above_starting_gesture_event_ &&
254 (curr_span_ > prev_span_));
255 const float span_diff =
256 (std::abs(1.f - (curr_span_ / prev_span_)) * kScaleFactor);
257 return prev_span_ <= 0 ? 1.f
258 : (scale_up ? (1.f + span_diff) : (1.f - span_diff));
260 return prev_span_ > 0 ? curr_span_ / prev_span_ : 1;
263 base::TimeDelta ScaleGestureDetector::GetTimeDelta() const {
264 return curr_time_ - prev_time_;
267 base::TimeTicks ScaleGestureDetector::GetEventTime() const {
268 return curr_time_;
271 bool ScaleGestureDetector::OnDoubleTap(const MotionEvent& ev) {
272 // Double tap: start watching for a swipe.
273 double_tap_focus_x_ = ev.GetX();
274 double_tap_focus_y_ = ev.GetY();
275 double_tap_mode_ = DOUBLE_TAP_MODE_IN_PROGRESS;
276 return true;
279 void ScaleGestureDetector::AddTouchHistory(const MotionEvent& ev) {
280 const base::TimeTicks current_time = ev.GetEventTime();
281 DCHECK(!current_time.is_null());
282 const int count = static_cast<int>(ev.GetPointerCount());
283 bool accept = touch_history_last_accepted_time_.is_null() ||
284 (current_time - touch_history_last_accepted_time_) >=
285 base::TimeDelta::FromMilliseconds(kTouchStabilizeTimeMs);
286 float total = 0;
287 int sample_count = 0;
288 for (int i = 0; i < count; i++) {
289 const bool has_last_accepted = !std::isnan(touch_history_last_accepted_);
290 const int history_size = static_cast<int>(ev.GetHistorySize());
291 const int pointersample_count = history_size + 1;
292 for (int h = 0; h < pointersample_count; h++) {
293 float major;
294 if (h < history_size) {
295 major = ev.GetHistoricalTouchMajor(i, h);
296 } else {
297 major = ev.GetTouchMajor(i);
299 if (major < touch_min_major_)
300 major = touch_min_major_;
301 if (major > touch_max_major_)
302 major = touch_max_major_;
303 total += major;
305 if (std::isnan(touch_upper_) || major > touch_upper_) {
306 touch_upper_ = major;
308 if (std::isnan(touch_lower_) || major < touch_lower_) {
309 touch_lower_ = major;
312 if (has_last_accepted) {
313 const float major_delta = major - touch_history_last_accepted_;
314 const int direction_sig =
315 major_delta > 0 ? 1 : (major_delta < 0 ? -1 : 0);
316 if (direction_sig != touch_history_direction_ ||
317 (direction_sig == 0 && touch_history_direction_ == 0)) {
318 touch_history_direction_ = direction_sig;
319 touch_history_last_accepted_time_ = h < history_size
320 ? ev.GetHistoricalEventTime(h)
321 : ev.GetEventTime();
322 accept = false;
326 sample_count += pointersample_count;
329 const float avg = total / sample_count;
331 if (accept) {
332 float new_accepted = (touch_upper_ + touch_lower_ + avg) / 3;
333 touch_upper_ = (touch_upper_ + new_accepted) / 2;
334 touch_lower_ = (touch_lower_ + new_accepted) / 2;
335 touch_history_last_accepted_ = new_accepted;
336 touch_history_direction_ = 0;
337 touch_history_last_accepted_time_ = ev.GetEventTime();
341 void ScaleGestureDetector::ResetTouchHistory() {
342 touch_upper_ = std::numeric_limits<float>::quiet_NaN();
343 touch_lower_ = std::numeric_limits<float>::quiet_NaN();
344 touch_history_last_accepted_ = std::numeric_limits<float>::quiet_NaN();
345 touch_history_direction_ = 0;
346 touch_history_last_accepted_time_ = base::TimeTicks();
349 void ScaleGestureDetector::ResetScaleWithSpan(float span) {
350 in_progress_ = false;
351 initial_span_ = span;
352 double_tap_mode_ = DOUBLE_TAP_MODE_NONE;
355 } // namespace ui