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/gfx/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 bool ApproxEquals(float a
, float b
) {
29 return std::abs(a
- b
) < kEpsilon
;
32 struct ViscosityConstants
{
34 : viscous_fluid_scale_(8.f
), viscous_fluid_normalize_(1.f
) {
35 viscous_fluid_normalize_
= 1.0f
/ ApplyViscosity(1.0f
);
38 float ApplyViscosity(float x
) {
39 x
*= viscous_fluid_scale_
;
41 x
-= (1.0f
- std::exp(-x
));
43 float start
= 0.36787944117f
; // 1/e == exp(-1)
44 x
= 1.0f
- std::exp(1.0f
- x
);
45 x
= start
+ x
* (1.0f
- start
);
47 x
*= viscous_fluid_normalize_
;
52 // This controls the intensity of the viscous fluid effect.
53 float viscous_fluid_scale_
;
54 float viscous_fluid_normalize_
;
56 DISALLOW_COPY_AND_ASSIGN(ViscosityConstants
);
59 struct SplineConstants
{
61 const float kStartTension
= 0.5f
;
62 const float kEndTension
= 1.0f
;
63 const float kP1
= kStartTension
* kInflexion
;
64 const float kP2
= 1.0f
- kEndTension
* (1.0f
- kInflexion
);
68 for (int i
= 0; i
< NUM_SAMPLES
; i
++) {
69 const float alpha
= static_cast<float>(i
) / NUM_SAMPLES
;
74 x
= x_min
+ (x_max
- x_min
) / 2.0f
;
75 coef
= 3.0f
* x
* (1.0f
- x
);
76 tx
= coef
* ((1.0f
- x
) * kP1
+ x
* kP2
) + x
* x
* x
;
77 if (ApproxEquals(tx
, alpha
))
84 spline_position_
[i
] = coef
* ((1.0f
- x
) * kStartTension
+ x
) + x
* x
* x
;
89 y
= y_min
+ (y_max
- y_min
) / 2.0f
;
90 coef
= 3.0f
* y
* (1.0f
- y
);
91 dy
= coef
* ((1.0f
- y
) * kStartTension
+ y
) + y
* y
* y
;
92 if (ApproxEquals(dy
, alpha
))
99 spline_time_
[i
] = coef
* ((1.0f
- y
) * kP1
+ y
* kP2
) + y
* y
* y
;
101 spline_position_
[NUM_SAMPLES
] = spline_time_
[NUM_SAMPLES
] = 1.0f
;
104 void CalculateCoefficients(float t
,
105 float* distance_coef
,
106 float* velocity_coef
) {
107 *distance_coef
= 1.f
;
108 *velocity_coef
= 0.f
;
109 const int index
= static_cast<int>(NUM_SAMPLES
* t
);
110 if (index
< NUM_SAMPLES
) {
111 const float t_inf
= static_cast<float>(index
) / NUM_SAMPLES
;
112 const float t_sup
= static_cast<float>(index
+ 1) / NUM_SAMPLES
;
113 const float d_inf
= spline_position_
[index
];
114 const float d_sup
= spline_position_
[index
+ 1];
115 *velocity_coef
= (d_sup
- d_inf
) / (t_sup
- t_inf
);
116 *distance_coef
= d_inf
+ (t
- t_inf
) * *velocity_coef
;
125 float spline_position_
[NUM_SAMPLES
+ 1];
126 float spline_time_
[NUM_SAMPLES
+ 1];
128 DISALLOW_COPY_AND_ASSIGN(SplineConstants
);
131 float ComputeDeceleration(float friction
) {
132 const float kGravityEarth
= 9.80665f
;
133 return kGravityEarth
// g (m/s^2)
134 * 39.37f
// inch/meter
135 * 160.f
// pixels/inch
139 template <typename T
>
141 return (T(0) < t
) - (t
< T(0));
144 template <typename T
>
145 T
Clamped(T t
, T a
, T b
) {
146 return t
< a
? a
: (t
> b
? b
: t
);
149 // Leaky to allow access from the impl thread.
150 base::LazyInstance
<ViscosityConstants
>::Leaky g_viscosity_constants
=
151 LAZY_INSTANCE_INITIALIZER
;
153 base::LazyInstance
<SplineConstants
>::Leaky g_spline_constants
=
154 LAZY_INSTANCE_INITIALIZER
;
158 Scroller::Config::Config()
159 : fling_friction(kDefaultFriction
),
160 flywheel_enabled(false) {}
162 Scroller::Scroller(const Config
& config
)
174 duration_seconds_reciprocal_(1),
180 flywheel_enabled_(config
.flywheel_enabled
),
184 fling_friction_(config
.fling_friction
),
185 deceleration_(ComputeDeceleration(fling_friction_
)),
186 tuning_coeff_(ComputeDeceleration(0.84f
)) {}
188 Scroller::~Scroller() {}
190 void Scroller::StartScroll(float start_x
,
194 base::TimeTicks start_time
) {
200 base::TimeDelta::FromMilliseconds(kDefaultDurationMs
));
203 void Scroller::StartScroll(float start_x
,
207 base::TimeTicks start_time
,
208 base::TimeDelta duration
) {
211 duration_
= duration
;
212 duration_seconds_reciprocal_
= 1.0 / duration_
.InSecondsF();
213 start_time_
= start_time
;
214 curr_x_
= start_x_
= start_x
;
215 curr_y_
= start_y_
= start_y
;
216 final_x_
= start_x
+ dx
;
217 final_y_
= start_y
+ dy
;
219 curr_time_
= start_time_
;
222 void Scroller::Fling(float start_x
,
230 base::TimeTicks start_time
) {
231 // Continue a scroll or fling in progress.
232 if (flywheel_enabled_
&& !finished_
) {
233 float old_velocity_x
= GetCurrVelocityX();
234 float old_velocity_y
= GetCurrVelocityY();
235 if (Signum(velocity_x
) == Signum(old_velocity_x
) &&
236 Signum(velocity_y
) == Signum(old_velocity_y
)) {
237 velocity_x
+= old_velocity_x
;
238 velocity_y
+= old_velocity_y
;
245 float velocity
= std::sqrt(velocity_x
* velocity_x
+ velocity_y
* velocity_y
);
247 velocity_
= velocity
;
248 duration_
= GetSplineFlingDuration(velocity
);
249 duration_seconds_reciprocal_
= 1.0 / duration_
.InSecondsF();
250 start_time_
= start_time
;
251 curr_time_
= start_time_
;
252 curr_x_
= start_x_
= start_x
;
253 curr_y_
= start_y_
= start_y
;
255 float coeff_x
= velocity
== 0 ? 1.0f
: velocity_x
/ velocity
;
256 float coeff_y
= velocity
== 0 ? 1.0f
: velocity_y
/ velocity
;
258 double total_distance
= GetSplineFlingDistance(velocity
);
259 distance_
= total_distance
* Signum(velocity
);
266 final_x_
= start_x
+ total_distance
* coeff_x
;
267 final_x_
= Clamped(final_x_
, min_x_
, max_x_
);
269 final_y_
= start_y
+ total_distance
* coeff_y
;
270 final_y_
= Clamped(final_y_
, min_y_
, max_y_
);
275 bool Scroller::ComputeScrollOffset(base::TimeTicks time
) {
279 if (time
== curr_time_
)
282 base::TimeDelta time_passed
= time
- start_time_
;
284 if (time_passed
< base::TimeDelta()) {
285 time_passed
= base::TimeDelta();
288 if (time_passed
>= duration_
) {
291 curr_time_
= start_time_
+ duration_
;
298 const float t
= time_passed
.InSecondsF() * duration_seconds_reciprocal_
;
302 NOTREACHED() << "|StartScroll()| or |Fling()| must be called prior to "
303 "scroll offset computation.";
307 float x
= g_viscosity_constants
.Get().ApplyViscosity(t
);
309 curr_x_
= start_x_
+ x
* delta_x_
;
310 curr_y_
= start_y_
+ x
* delta_y_
;
314 float distance_coef
= 1.f
;
315 float velocity_coef
= 0.f
;
316 g_spline_constants
.Get().CalculateCoefficients(
317 t
, &distance_coef
, &velocity_coef
);
319 curr_velocity_
= velocity_coef
* distance_
* duration_seconds_reciprocal_
;
321 curr_x_
= start_x_
+ distance_coef
* delta_x_
;
322 curr_x_
= Clamped(curr_x_
, min_x_
, max_x_
);
324 curr_y_
= start_y_
+ distance_coef
* delta_y_
;
325 curr_y_
= Clamped(curr_y_
, min_y_
, max_y_
);
327 if (ApproxEquals(curr_x_
, final_x_
) && ApproxEquals(curr_y_
, final_y_
)) {
336 void Scroller::ExtendDuration(base::TimeDelta extend
) {
337 base::TimeDelta passed
= GetTimePassed();
338 duration_
= passed
+ extend
;
339 duration_seconds_reciprocal_
= 1.0 / duration_
.InSecondsF();
343 void Scroller::SetFinalX(float new_x
) {
349 void Scroller::SetFinalY(float new_y
) {
355 void Scroller::AbortAnimation() {
359 curr_time_
= start_time_
+ duration_
;
363 void Scroller::ForceFinished(bool finished
) { finished_
= finished
; }
365 bool Scroller::IsFinished() const { return finished_
; }
367 base::TimeDelta
Scroller::GetTimePassed() const {
368 return curr_time_
- start_time_
;
371 base::TimeDelta
Scroller::GetDuration() const { return duration_
; }
373 float Scroller::GetCurrX() const { return curr_x_
; }
375 float Scroller::GetCurrY() const { return curr_y_
; }
377 float Scroller::GetCurrVelocity() const {
380 if (mode_
== FLING_MODE
)
381 return curr_velocity_
;
382 return velocity_
- deceleration_
* GetTimePassed().InSecondsF() * 0.5f
;
385 float Scroller::GetCurrVelocityX() const {
386 return delta_x_norm_
* GetCurrVelocity();
389 float Scroller::GetCurrVelocityY() const {
390 return delta_y_norm_
* GetCurrVelocity();
393 float Scroller::GetStartX() const { return start_x_
; }
395 float Scroller::GetStartY() const { return start_y_
; }
397 float Scroller::GetFinalX() const { return final_x_
; }
399 float Scroller::GetFinalY() const { return final_y_
; }
401 bool Scroller::IsScrollingInDirection(float xvel
, float yvel
) const {
402 return !finished_
&& Signum(xvel
) == Signum(delta_x_
) &&
403 Signum(yvel
) == Signum(delta_y_
);
406 void Scroller::RecomputeDeltas() {
407 delta_x_
= final_x_
- start_x_
;
408 delta_y_
= final_y_
- start_y_
;
410 const float hyp
= std::sqrt(delta_x_
* delta_x_
+ delta_y_
* delta_y_
);
411 if (hyp
> kEpsilon
) {
412 delta_x_norm_
= delta_x_
/ hyp
;
413 delta_y_norm_
= delta_y_
/ hyp
;
415 delta_x_norm_
= delta_y_norm_
= 1;
419 double Scroller::GetSplineDeceleration(float velocity
) const {
420 return std::log(kInflexion
* std::abs(velocity
) /
421 (fling_friction_
* tuning_coeff_
));
424 base::TimeDelta
Scroller::GetSplineFlingDuration(float velocity
) const {
425 const double l
= GetSplineDeceleration(velocity
);
426 const double decel_minus_one
= kDecelerationRate
- 1.0;
427 const double time_seconds
= std::exp(l
/ decel_minus_one
);
428 return base::TimeDelta::FromMicroseconds(time_seconds
*
429 base::Time::kMicrosecondsPerSecond
);
432 double Scroller::GetSplineFlingDistance(float velocity
) const {
433 const double l
= GetSplineDeceleration(velocity
);
434 const double decel_minus_one
= kDecelerationRate
- 1.0;
435 return fling_friction_
* tuning_coeff_
*
436 std::exp(kDecelerationRate
/ decel_minus_one
* l
);