Upstream version 11.40.271.0
[platform/framework/web/crosswalk.git] / src / ui / events / android / scroller.cc
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/android/scroller.h"
6
7 #include <cmath>
8
9 #include "base/lazy_instance.h"
10
11 namespace ui {
12 namespace {
13
14 // Default scroll duration from android.widget.Scroller.
15 const int kDefaultDurationMs = 250;
16
17 // Default friction constant in android.view.ViewConfiguration.
18 const float kDefaultFriction = 0.015f;
19
20 // == std::log(0.78f) / std::log(0.9f)
21 const float kDecelerationRate = 2.3582018f;
22
23 // Tension lines cross at (kInflexion, 1).
24 const float kInflexion = 0.35f;
25
26 const float kEpsilon = 1e-5f;
27
28 // Fling scroll is stopped when the scroll position is |kThresholdForFlingEnd|
29 // pixels or closer from the end.
30 const float kThresholdForFlingEnd = 0.1;
31
32 bool ApproxEquals(float a, float b) {
33   return std::abs(a - b) < kEpsilon;
34 }
35
36 struct ViscosityConstants {
37   ViscosityConstants()
38       : viscous_fluid_scale_(8.f), viscous_fluid_normalize_(1.f) {
39     viscous_fluid_normalize_ = 1.0f / ApplyViscosity(1.0f);
40   }
41
42   float ApplyViscosity(float x) {
43     x *= viscous_fluid_scale_;
44     if (x < 1.0f) {
45       x -= (1.0f - std::exp(-x));
46     } else {
47       float start = 0.36787944117f;  // 1/e == exp(-1)
48       x = 1.0f - std::exp(1.0f - x);
49       x = start + x * (1.0f - start);
50     }
51     x *= viscous_fluid_normalize_;
52     return x;
53   }
54
55  private:
56   // This controls the intensity of the viscous fluid effect.
57   float viscous_fluid_scale_;
58   float viscous_fluid_normalize_;
59
60   DISALLOW_COPY_AND_ASSIGN(ViscosityConstants);
61 };
62
63 struct SplineConstants {
64   SplineConstants() {
65     const float kStartTension = 0.5f;
66     const float kEndTension = 1.0f;
67     const float kP1 = kStartTension * kInflexion;
68     const float kP2 = 1.0f - kEndTension * (1.0f - kInflexion);
69
70     float x_min = 0.0f;
71     float y_min = 0.0f;
72     for (int i = 0; i < NUM_SAMPLES; i++) {
73       const float alpha = static_cast<float>(i) / NUM_SAMPLES;
74
75       float x_max = 1.0f;
76       float x, tx, coef;
77       while (true) {
78         x = x_min + (x_max - x_min) / 2.0f;
79         coef = 3.0f * x * (1.0f - x);
80         tx = coef * ((1.0f - x) * kP1 + x * kP2) + x * x * x;
81         if (ApproxEquals(tx, alpha))
82           break;
83         if (tx > alpha)
84           x_max = x;
85         else
86           x_min = x;
87       }
88       spline_position_[i] = coef * ((1.0f - x) * kStartTension + x) + x * x * x;
89
90       float y_max = 1.0f;
91       float y, dy;
92       while (true) {
93         y = y_min + (y_max - y_min) / 2.0f;
94         coef = 3.0f * y * (1.0f - y);
95         dy = coef * ((1.0f - y) * kStartTension + y) + y * y * y;
96         if (ApproxEquals(dy, alpha))
97           break;
98         if (dy > alpha)
99           y_max = y;
100         else
101           y_min = y;
102       }
103       spline_time_[i] = coef * ((1.0f - y) * kP1 + y * kP2) + y * y * y;
104     }
105     spline_position_[NUM_SAMPLES] = spline_time_[NUM_SAMPLES] = 1.0f;
106   }
107
108   void CalculateCoefficients(float t,
109                              float* distance_coef,
110                              float* velocity_coef) {
111     *distance_coef = 1.f;
112     *velocity_coef = 0.f;
113     const int index = static_cast<int>(NUM_SAMPLES * t);
114     if (index < NUM_SAMPLES) {
115       const float t_inf = static_cast<float>(index) / NUM_SAMPLES;
116       const float t_sup = static_cast<float>(index + 1) / NUM_SAMPLES;
117       const float d_inf = spline_position_[index];
118       const float d_sup = spline_position_[index + 1];
119       *velocity_coef = (d_sup - d_inf) / (t_sup - t_inf);
120       *distance_coef = d_inf + (t - t_inf) * *velocity_coef;
121     }
122   }
123
124  private:
125   enum { NUM_SAMPLES = 100 };
126
127   float spline_position_[NUM_SAMPLES + 1];
128   float spline_time_[NUM_SAMPLES + 1];
129
130   DISALLOW_COPY_AND_ASSIGN(SplineConstants);
131 };
132
133 float ComputeDeceleration(float friction) {
134   const float kGravityEarth = 9.80665f;
135   return kGravityEarth  // g (m/s^2)
136          * 39.37f       // inch/meter
137          * 160.f        // pixels/inch
138          * friction;
139 }
140
141 template <typename T>
142 int Signum(T t) {
143   return (T(0) < t) - (t < T(0));
144 }
145
146 template <typename T>
147 T Clamped(T t, T a, T b) {
148   return t < a ? a : (t > b ? b : t);
149 }
150
151 // Leaky to allow access from the impl thread.
152 base::LazyInstance<ViscosityConstants>::Leaky g_viscosity_constants =
153     LAZY_INSTANCE_INITIALIZER;
154
155 base::LazyInstance<SplineConstants>::Leaky g_spline_constants =
156     LAZY_INSTANCE_INITIALIZER;
157
158 }  // namespace
159
160 Scroller::Config::Config()
161     : fling_friction(kDefaultFriction), flywheel_enabled(false) {
162 }
163
164 Scroller::Scroller(const Config& config)
165     : mode_(UNDEFINED),
166       start_x_(0),
167       start_y_(0),
168       final_x_(0),
169       final_y_(0),
170       min_x_(0),
171       max_x_(0),
172       min_y_(0),
173       max_y_(0),
174       curr_x_(0),
175       curr_y_(0),
176       duration_seconds_reciprocal_(1),
177       delta_x_(0),
178       delta_x_norm_(1),
179       delta_y_(0),
180       delta_y_norm_(1),
181       finished_(true),
182       flywheel_enabled_(config.flywheel_enabled),
183       velocity_(0),
184       curr_velocity_(0),
185       distance_(0),
186       fling_friction_(config.fling_friction),
187       deceleration_(ComputeDeceleration(fling_friction_)),
188       tuning_coeff_(ComputeDeceleration(0.84f)) {
189 }
190
191 Scroller::~Scroller() {
192 }
193
194 bool Scroller::ComputeScrollOffset(base::TimeTicks time,
195                                    gfx::Vector2dF* offset,
196                                    gfx::Vector2dF* velocity) {
197   DCHECK(offset);
198   DCHECK(velocity);
199   if (!ComputeScrollOffsetInternal(time)) {
200     *offset = gfx::Vector2dF(GetFinalX(), GetFinalY());
201     *velocity = gfx::Vector2dF();
202     return false;
203   }
204
205   *offset = gfx::Vector2dF(GetCurrX(), GetCurrY());
206   *velocity = gfx::Vector2dF(GetCurrVelocityX(), GetCurrVelocityY());
207   return true;
208 }
209
210 void Scroller::StartScroll(float start_x,
211                            float start_y,
212                            float dx,
213                            float dy,
214                            base::TimeTicks start_time) {
215   StartScroll(start_x,
216               start_y,
217               dx,
218               dy,
219               start_time,
220               base::TimeDelta::FromMilliseconds(kDefaultDurationMs));
221 }
222
223 void Scroller::StartScroll(float start_x,
224                            float start_y,
225                            float dx,
226                            float dy,
227                            base::TimeTicks start_time,
228                            base::TimeDelta duration) {
229   DCHECK_GT(duration.ToInternalValue(), 0);
230   mode_ = SCROLL_MODE;
231   finished_ = false;
232   duration_ = duration;
233   duration_seconds_reciprocal_ = 1.0 / duration_.InSecondsF();
234   start_time_ = start_time;
235   curr_x_ = start_x_ = start_x;
236   curr_y_ = start_y_ = start_y;
237   final_x_ = start_x + dx;
238   final_y_ = start_y + dy;
239   RecomputeDeltas();
240   curr_time_ = start_time_;
241 }
242
243 void Scroller::Fling(float start_x,
244                      float start_y,
245                      float velocity_x,
246                      float velocity_y,
247                      float min_x,
248                      float max_x,
249                      float min_y,
250                      float max_y,
251                      base::TimeTicks start_time) {
252   DCHECK(velocity_x || velocity_y);
253
254   // Continue a scroll or fling in progress.
255   if (flywheel_enabled_ && !finished_) {
256     float old_velocity_x = GetCurrVelocityX();
257     float old_velocity_y = GetCurrVelocityY();
258     if (Signum(velocity_x) == Signum(old_velocity_x) &&
259         Signum(velocity_y) == Signum(old_velocity_y)) {
260       velocity_x += old_velocity_x;
261       velocity_y += old_velocity_y;
262     }
263   }
264
265   mode_ = FLING_MODE;
266   finished_ = false;
267
268   float velocity = std::sqrt(velocity_x * velocity_x + velocity_y * velocity_y);
269
270   velocity_ = velocity;
271   duration_ = GetSplineFlingDuration(velocity);
272   DCHECK_GT(duration_.ToInternalValue(), 0);
273   duration_seconds_reciprocal_ = 1.0 / duration_.InSecondsF();
274   start_time_ = start_time;
275   curr_time_ = start_time_;
276   curr_x_ = start_x_ = start_x;
277   curr_y_ = start_y_ = start_y;
278
279   float coeff_x = velocity == 0 ? 1.0f : velocity_x / velocity;
280   float coeff_y = velocity == 0 ? 1.0f : velocity_y / velocity;
281
282   double total_distance = GetSplineFlingDistance(velocity);
283   distance_ = total_distance * Signum(velocity);
284
285   min_x_ = min_x;
286   max_x_ = max_x;
287   min_y_ = min_y;
288   max_y_ = max_y;
289
290   final_x_ = start_x + total_distance * coeff_x;
291   final_x_ = Clamped(final_x_, min_x_, max_x_);
292
293   final_y_ = start_y + total_distance * coeff_y;
294   final_y_ = Clamped(final_y_, min_y_, max_y_);
295
296   RecomputeDeltas();
297 }
298
299 void Scroller::ExtendDuration(base::TimeDelta extend) {
300   base::TimeDelta passed = GetTimePassed();
301   duration_ = passed + extend;
302   duration_seconds_reciprocal_ = 1.0 / duration_.InSecondsF();
303   finished_ = false;
304 }
305
306 void Scroller::SetFinalX(float new_x) {
307   final_x_ = new_x;
308   finished_ = false;
309   RecomputeDeltas();
310 }
311
312 void Scroller::SetFinalY(float new_y) {
313   final_y_ = new_y;
314   finished_ = false;
315   RecomputeDeltas();
316 }
317
318 void Scroller::AbortAnimation() {
319   curr_x_ = final_x_;
320   curr_y_ = final_y_;
321   curr_velocity_ = 0;
322   curr_time_ = start_time_ + duration_;
323   finished_ = true;
324 }
325
326 void Scroller::ForceFinished(bool finished) {
327   finished_ = finished;
328 }
329
330 bool Scroller::IsFinished() const {
331   return finished_;
332 }
333
334 base::TimeDelta Scroller::GetTimePassed() const {
335   return curr_time_ - start_time_;
336 }
337
338 base::TimeDelta Scroller::GetDuration() const {
339   return duration_;
340 }
341
342 float Scroller::GetCurrX() const {
343   return curr_x_;
344 }
345
346 float Scroller::GetCurrY() const {
347   return curr_y_;
348 }
349
350 float Scroller::GetCurrVelocity() const {
351   if (finished_)
352     return 0;
353   if (mode_ == FLING_MODE)
354     return curr_velocity_;
355   return velocity_ - deceleration_ * GetTimePassed().InSecondsF() * 0.5f;
356 }
357
358 float Scroller::GetCurrVelocityX() const {
359   return delta_x_norm_ * GetCurrVelocity();
360 }
361
362 float Scroller::GetCurrVelocityY() const {
363   return delta_y_norm_ * GetCurrVelocity();
364 }
365
366 float Scroller::GetStartX() const {
367   return start_x_;
368 }
369
370 float Scroller::GetStartY() const {
371   return start_y_;
372 }
373
374 float Scroller::GetFinalX() const {
375   return final_x_;
376 }
377
378 float Scroller::GetFinalY() const {
379   return final_y_;
380 }
381
382 bool Scroller::IsScrollingInDirection(float xvel, float yvel) const {
383   return !finished_ && Signum(xvel) == Signum(delta_x_) &&
384          Signum(yvel) == Signum(delta_y_);
385 }
386
387 bool Scroller::ComputeScrollOffsetInternal(base::TimeTicks time) {
388   if (finished_)
389     return false;
390
391   if (time <= start_time_)
392     return true;
393
394   if (time == curr_time_)
395     return true;
396
397   base::TimeDelta time_passed = time - start_time_;
398   if (time_passed >= duration_) {
399     AbortAnimation();
400     return false;
401   }
402
403   curr_time_ = time;
404
405   const float u = time_passed.InSecondsF() * duration_seconds_reciprocal_;
406   switch (mode_) {
407     case UNDEFINED:
408       NOTREACHED() << "|StartScroll()| or |Fling()| must be called prior to "
409                       "scroll offset computation.";
410       return false;
411
412     case SCROLL_MODE: {
413       float x = g_viscosity_constants.Get().ApplyViscosity(u);
414
415       curr_x_ = start_x_ + x * delta_x_;
416       curr_y_ = start_y_ + x * delta_y_;
417     } break;
418
419     case FLING_MODE: {
420       float distance_coef = 1.f;
421       float velocity_coef = 0.f;
422       g_spline_constants.Get().CalculateCoefficients(
423           u, &distance_coef, &velocity_coef);
424
425       curr_velocity_ = velocity_coef * distance_ * duration_seconds_reciprocal_;
426
427       curr_x_ = start_x_ + distance_coef * delta_x_;
428       curr_x_ = Clamped(curr_x_, min_x_, max_x_);
429
430       curr_y_ = start_y_ + distance_coef * delta_y_;
431       curr_y_ = Clamped(curr_y_, min_y_, max_y_);
432
433       float diff_x = std::abs(curr_x_ - final_x_);
434       float diff_y = std::abs(curr_y_ - final_y_);
435       if (diff_x < kThresholdForFlingEnd && diff_y < kThresholdForFlingEnd)
436         AbortAnimation();
437     } break;
438   }
439
440   return !finished_;
441 }
442
443 void Scroller::RecomputeDeltas() {
444   delta_x_ = final_x_ - start_x_;
445   delta_y_ = final_y_ - start_y_;
446
447   const float hyp = std::sqrt(delta_x_ * delta_x_ + delta_y_ * delta_y_);
448   if (hyp > kEpsilon) {
449     delta_x_norm_ = delta_x_ / hyp;
450     delta_y_norm_ = delta_y_ / hyp;
451   } else {
452     delta_x_norm_ = delta_y_norm_ = 1;
453   }
454 }
455
456 double Scroller::GetSplineDeceleration(float velocity) const {
457   return std::log(kInflexion * std::abs(velocity) /
458                   (fling_friction_ * tuning_coeff_));
459 }
460
461 base::TimeDelta Scroller::GetSplineFlingDuration(float velocity) const {
462   const double l = GetSplineDeceleration(velocity);
463   const double decel_minus_one = kDecelerationRate - 1.0;
464   const double time_seconds = std::exp(l / decel_minus_one);
465   return base::TimeDelta::FromMicroseconds(time_seconds *
466                                            base::Time::kMicrosecondsPerSecond);
467 }
468
469 double Scroller::GetSplineFlingDistance(float velocity) const {
470   const double l = GetSplineDeceleration(velocity);
471   const double decel_minus_one = kDecelerationRate - 1.0;
472   return fling_friction_ * tuning_coeff_ *
473          std::exp(kDecelerationRate / decel_minus_one * l);
474 }
475
476 }  // namespace ui