mobile_scroller.cc 13 KB

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