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/android/scroller.h"
9 #include "base/lazy_instance.h"
14 // Default scroll duration from android.widget.Scroller.
15 const int kDefaultDurationMs
= 250;
17 // Default friction constant in android.view.ViewConfiguration.
18 const float kDefaultFriction
= 0.015f
;
20 // == std::log(0.78f) / std::log(0.9f)
21 const float kDecelerationRate
= 2.3582018f
;
23 // Tension lines cross at (kInflexion, 1).
24 const float kInflexion
= 0.35f
;
26 const float kEpsilon
= 1e-5f
;
28 // Fling scroll is stopped when the scroll position is |kThresholdForFlingEnd|
29 // pixels or closer from the end.
30 const float kThresholdForFlingEnd
= 0.1;
32 bool ApproxEquals(float a
, float b
) {
33 return std::abs(a
- b
) < kEpsilon
;
36 struct ViscosityConstants
{
38 : viscous_fluid_scale_(8.f
), viscous_fluid_normalize_(1.f
) {
39 viscous_fluid_normalize_
= 1.0f
/ ApplyViscosity(1.0f
);
42 float ApplyViscosity(float x
) {
43 x
*= viscous_fluid_scale_
;
45 x
-= (1.0f
- std::exp(-x
));
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
);
51 x
*= viscous_fluid_normalize_
;
56 // This controls the intensity of the viscous fluid effect.
57 float viscous_fluid_scale_
;
58 float viscous_fluid_normalize_
;
60 DISALLOW_COPY_AND_ASSIGN(ViscosityConstants
);
63 struct 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
);
72 for (int i
= 0; i
< NUM_SAMPLES
; i
++) {
73 const float alpha
= static_cast<float>(i
) / NUM_SAMPLES
;
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
))
88 spline_position_
[i
] = coef
* ((1.0f
- x
) * kStartTension
+ x
) + x
* x
* x
;
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
))
103 spline_time_
[i
] = coef
* ((1.0f
- y
) * kP1
+ y
* kP2
) + y
* y
* y
;
105 spline_position_
[NUM_SAMPLES
] = spline_time_
[NUM_SAMPLES
] = 1.0f
;
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
;
125 enum { NUM_SAMPLES
= 100 };
127 float spline_position_
[NUM_SAMPLES
+ 1];
128 float spline_time_
[NUM_SAMPLES
+ 1];
130 DISALLOW_COPY_AND_ASSIGN(SplineConstants
);
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
141 template <typename T
>
143 return (T(0) < t
) - (t
< T(0));
146 template <typename T
>
147 T
Clamped(T t
, T a
, T b
) {
148 return t
< a
? a
: (t
> b
? b
: t
);
151 // Leaky to allow access from the impl thread.
152 base::LazyInstance
<ViscosityConstants
>::Leaky g_viscosity_constants
=
153 LAZY_INSTANCE_INITIALIZER
;
155 base::LazyInstance
<SplineConstants
>::Leaky g_spline_constants
=
156 LAZY_INSTANCE_INITIALIZER
;
160 Scroller::Config::Config()
161 : fling_friction(kDefaultFriction
), flywheel_enabled(false) {
164 Scroller::Scroller(const Config
& config
)
176 duration_seconds_reciprocal_(1),
182 flywheel_enabled_(config
.flywheel_enabled
),
186 fling_friction_(config
.fling_friction
),
187 deceleration_(ComputeDeceleration(fling_friction_
)),
188 tuning_coeff_(ComputeDeceleration(0.84f
)) {
191 Scroller::~Scroller() {
194 bool Scroller::ComputeScrollOffset(base::TimeTicks time
,
195 gfx::Vector2dF
* offset
,
196 gfx::Vector2dF
* velocity
) {
199 if (!ComputeScrollOffsetInternal(time
)) {
200 *offset
= gfx::Vector2dF(GetFinalX(), GetFinalY());
201 *velocity
= gfx::Vector2dF();
205 *offset
= gfx::Vector2dF(GetCurrX(), GetCurrY());
206 *velocity
= gfx::Vector2dF(GetCurrVelocityX(), GetCurrVelocityY());
210 void Scroller::StartScroll(float start_x
,
214 base::TimeTicks start_time
) {
220 base::TimeDelta::FromMilliseconds(kDefaultDurationMs
));
223 void Scroller::StartScroll(float start_x
,
227 base::TimeTicks start_time
,
228 base::TimeDelta duration
) {
229 DCHECK_GT(duration
.ToInternalValue(), 0);
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
;
240 curr_time_
= start_time_
;
243 void Scroller::Fling(float start_x
,
251 base::TimeTicks start_time
) {
252 DCHECK(velocity_x
|| velocity_y
);
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
;
268 float velocity
= std::sqrt(velocity_x
* velocity_x
+ velocity_y
* velocity_y
);
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
;
279 float coeff_x
= velocity
== 0 ? 1.0f
: velocity_x
/ velocity
;
280 float coeff_y
= velocity
== 0 ? 1.0f
: velocity_y
/ velocity
;
282 double total_distance
= GetSplineFlingDistance(velocity
);
283 distance_
= total_distance
* Signum(velocity
);
290 final_x_
= start_x
+ total_distance
* coeff_x
;
291 final_x_
= Clamped(final_x_
, min_x_
, max_x_
);
293 final_y_
= start_y
+ total_distance
* coeff_y
;
294 final_y_
= Clamped(final_y_
, min_y_
, max_y_
);
299 void Scroller::ExtendDuration(base::TimeDelta extend
) {
300 base::TimeDelta passed
= GetTimePassed();
301 duration_
= passed
+ extend
;
302 duration_seconds_reciprocal_
= 1.0 / duration_
.InSecondsF();
306 void Scroller::SetFinalX(float new_x
) {
312 void Scroller::SetFinalY(float new_y
) {
318 void Scroller::AbortAnimation() {
322 curr_time_
= start_time_
+ duration_
;
326 void Scroller::ForceFinished(bool finished
) {
327 finished_
= finished
;
330 bool Scroller::IsFinished() const {
334 base::TimeDelta
Scroller::GetTimePassed() const {
335 return curr_time_
- start_time_
;
338 base::TimeDelta
Scroller::GetDuration() const {
342 float Scroller::GetCurrX() const {
346 float Scroller::GetCurrY() const {
350 float Scroller::GetCurrVelocity() const {
353 if (mode_
== FLING_MODE
)
354 return curr_velocity_
;
355 return velocity_
- deceleration_
* GetTimePassed().InSecondsF() * 0.5f
;
358 float Scroller::GetCurrVelocityX() const {
359 return delta_x_norm_
* GetCurrVelocity();
362 float Scroller::GetCurrVelocityY() const {
363 return delta_y_norm_
* GetCurrVelocity();
366 float Scroller::GetStartX() const {
370 float Scroller::GetStartY() const {
374 float Scroller::GetFinalX() const {
378 float Scroller::GetFinalY() const {
382 bool Scroller::IsScrollingInDirection(float xvel
, float yvel
) const {
383 return !finished_
&& Signum(xvel
) == Signum(delta_x_
) &&
384 Signum(yvel
) == Signum(delta_y_
);
387 bool Scroller::ComputeScrollOffsetInternal(base::TimeTicks time
) {
391 if (time
<= start_time_
)
394 if (time
== curr_time_
)
397 base::TimeDelta time_passed
= time
- start_time_
;
398 if (time_passed
>= duration_
) {
405 const float u
= time_passed
.InSecondsF() * duration_seconds_reciprocal_
;
408 NOTREACHED() << "|StartScroll()| or |Fling()| must be called prior to "
409 "scroll offset computation.";
413 float x
= g_viscosity_constants
.Get().ApplyViscosity(u
);
415 curr_x_
= start_x_
+ x
* delta_x_
;
416 curr_y_
= start_y_
+ x
* delta_y_
;
420 float distance_coef
= 1.f
;
421 float velocity_coef
= 0.f
;
422 g_spline_constants
.Get().CalculateCoefficients(
423 u
, &distance_coef
, &velocity_coef
);
425 curr_velocity_
= velocity_coef
* distance_
* duration_seconds_reciprocal_
;
427 curr_x_
= start_x_
+ distance_coef
* delta_x_
;
428 curr_x_
= Clamped(curr_x_
, min_x_
, max_x_
);
430 curr_y_
= start_y_
+ distance_coef
* delta_y_
;
431 curr_y_
= Clamped(curr_y_
, min_y_
, max_y_
);
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
)
443 void Scroller::RecomputeDeltas() {
444 delta_x_
= final_x_
- start_x_
;
445 delta_y_
= final_y_
- start_y_
;
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
;
452 delta_x_norm_
= delta_y_norm_
= 1;
456 double Scroller::GetSplineDeceleration(float velocity
) const {
457 return std::log(kInflexion
* std::abs(velocity
) /
458 (fling_friction_
* tuning_coeff_
));
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
);
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
);