forked from mirrors/gecko-dev
Bug 1448439 - Add an AndroidFlingPhysics class containing an implementation of Chrome's fling physics on Android. r=kats
MozReview-Commit-ID: 509Cl04rozm --HG-- extra : rebase_source : 11a3ebd03430e0981be156638762987c7c07a458
This commit is contained in:
parent
2796cb0198
commit
1836235d39
4 changed files with 239 additions and 0 deletions
190
gfx/layers/apz/src/AndroidFlingPhysics.cpp
Normal file
190
gfx/layers/apz/src/AndroidFlingPhysics.cpp
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include "AndroidFlingPhysics.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "mozilla/ClearOnShutdown.h"
|
||||||
|
#include "mozilla/StaticPtr.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace layers {
|
||||||
|
|
||||||
|
// The fling physics calculations implemented here are adapted from
|
||||||
|
// Chrome's implementation of fling physics on Android:
|
||||||
|
// https://cs.chromium.org/chromium/src/ui/events/android/scroller.cc?rcl=3ae3aaff927038a5c644926842cb0c31dea60c79
|
||||||
|
|
||||||
|
static double ComputeDeceleration(float aFriction)
|
||||||
|
{
|
||||||
|
const float kGravityEarth = 9.80665f;
|
||||||
|
return kGravityEarth // g (m/s^2)
|
||||||
|
* 39.37f // inch/meter
|
||||||
|
* 160.f // pixels/inch
|
||||||
|
* aFriction;
|
||||||
|
}
|
||||||
|
|
||||||
|
// == std::log(0.78f) / std::log(0.9f)
|
||||||
|
const float kDecelerationRate = 2.3582018f;
|
||||||
|
|
||||||
|
// Default friction constant in android.view.ViewConfiguration.
|
||||||
|
const float kFlingFriction = 0.015f;
|
||||||
|
|
||||||
|
// Tension lines cross at (kInflexion, 1).
|
||||||
|
const float kInflexion = 0.35f;
|
||||||
|
|
||||||
|
// Fling scroll is stopped when the scroll position is |kThresholdForFlingEnd|
|
||||||
|
// pixels or closer from the end.
|
||||||
|
const float kThresholdForFlingEnd = 0.1;
|
||||||
|
|
||||||
|
const float kTuningCoeff = ComputeDeceleration(0.84f);
|
||||||
|
|
||||||
|
static double ComputeSplineDeceleration(ParentLayerCoord aVelocity)
|
||||||
|
{
|
||||||
|
float velocityPerSec = aVelocity * 1000.0f;
|
||||||
|
return std::log(kInflexion * velocityPerSec / (kFlingFriction * kTuningCoeff));
|
||||||
|
}
|
||||||
|
|
||||||
|
static TimeDuration ComputeFlingDuration(ParentLayerCoord aVelocity)
|
||||||
|
{
|
||||||
|
const double splineDecel = ComputeSplineDeceleration(aVelocity);
|
||||||
|
const double timeSeconds = std::exp(splineDecel / (kDecelerationRate - 1.0));
|
||||||
|
return TimeDuration::FromSeconds(timeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ParentLayerCoord ComputeFlingDistance(ParentLayerCoord aVelocity)
|
||||||
|
{
|
||||||
|
const double splineDecel = ComputeSplineDeceleration(aVelocity);
|
||||||
|
return kFlingFriction * kTuningCoeff *
|
||||||
|
std::exp(kDecelerationRate / (kDecelerationRate - 1.0) * splineDecel);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SplineConstants {
|
||||||
|
public:
|
||||||
|
SplineConstants() {
|
||||||
|
const float kStartTension = 0.5f;
|
||||||
|
const float kEndTension = 1.0f;
|
||||||
|
const float kP1 = kStartTension * kInflexion;
|
||||||
|
const float kP2 = 1.0f - kEndTension * (1.0f - kInflexion);
|
||||||
|
|
||||||
|
float xMin = 0.0f;
|
||||||
|
for (int i = 0; i < kNumSamples; i++) {
|
||||||
|
const float alpha = static_cast<float>(i) / kNumSamples;
|
||||||
|
|
||||||
|
float xMax = 1.0f;
|
||||||
|
float x, tx, coef;
|
||||||
|
while (true) {
|
||||||
|
x = xMin + (xMax - xMin) / 2.0f;
|
||||||
|
coef = 3.0f * x * (1.0f - x);
|
||||||
|
tx = coef * ((1.0f - x) * kP1 + x * kP2) + x * x * x;
|
||||||
|
if (FuzzyEqualsAdditive(tx, alpha)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (tx > alpha) {
|
||||||
|
xMax = x;
|
||||||
|
} else {
|
||||||
|
xMin = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mSplinePositions[i] = coef * ((1.0f - x) * kStartTension + x) + x * x * x;
|
||||||
|
}
|
||||||
|
mSplinePositions[kNumSamples] = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalculateCoefficients(float aTime,
|
||||||
|
float* aOutDistanceCoef,
|
||||||
|
float* aOutVelocityCoef)
|
||||||
|
{
|
||||||
|
*aOutDistanceCoef = 1.0f;
|
||||||
|
*aOutVelocityCoef = 0.0f;
|
||||||
|
const int index = static_cast<int>(kNumSamples * aTime);
|
||||||
|
if (index < kNumSamples) {
|
||||||
|
const float tInf = static_cast<float>(index) / kNumSamples;
|
||||||
|
const float dInf = mSplinePositions[index];
|
||||||
|
const float tSup = static_cast<float>(index + 1) / kNumSamples;
|
||||||
|
const float dSup = mSplinePositions[index + 1];
|
||||||
|
*aOutVelocityCoef = (dSup - dInf) / (tSup - tInf);
|
||||||
|
*aOutDistanceCoef = dInf + (aTime - tInf) * *aOutVelocityCoef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
static const int kNumSamples = 100;
|
||||||
|
float mSplinePositions[kNumSamples + 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
StaticAutoPtr<SplineConstants> gSplineConstants;
|
||||||
|
|
||||||
|
/* static */ void AndroidFlingPhysics::InitializeGlobalState()
|
||||||
|
{
|
||||||
|
gSplineConstants = new SplineConstants();
|
||||||
|
ClearOnShutdown(&gSplineConstants);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidFlingPhysics::Init(const ParentLayerPoint& aStartingVelocity)
|
||||||
|
{
|
||||||
|
mVelocity = aStartingVelocity.Length();
|
||||||
|
mTargetDuration = ComputeFlingDuration(mVelocity);
|
||||||
|
MOZ_ASSERT(!mTargetDuration.IsZero());
|
||||||
|
mDurationSoFar = TimeDuration();
|
||||||
|
mLastPos = ParentLayerPoint();
|
||||||
|
mCurrentPos = ParentLayerPoint();
|
||||||
|
float coeffX = mVelocity == 0 ? 1.0f : aStartingVelocity.x / mVelocity;
|
||||||
|
float coeffY = mVelocity == 0 ? 1.0f : aStartingVelocity.y / mVelocity;
|
||||||
|
mTargetDistance = ComputeFlingDistance(mVelocity);
|
||||||
|
mTargetPos = ParentLayerPoint(mTargetDistance * coeffX,
|
||||||
|
mTargetDistance * coeffY);
|
||||||
|
const float hyp = mTargetPos.Length();
|
||||||
|
if (FuzzyEqualsAdditive(hyp, 0.0f)) {
|
||||||
|
mDeltaNorm = ParentLayerPoint(1, 1);
|
||||||
|
} else {
|
||||||
|
mDeltaNorm = ParentLayerPoint(mTargetPos.x / hyp, mTargetPos.y / hyp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void AndroidFlingPhysics::Sample(const TimeDuration& aDelta,
|
||||||
|
ParentLayerPoint* aOutVelocity,
|
||||||
|
ParentLayerPoint* aOutOffset)
|
||||||
|
{
|
||||||
|
float newVelocity;
|
||||||
|
if (SampleImpl(aDelta, &newVelocity)) {
|
||||||
|
*aOutOffset = (mCurrentPos - mLastPos);
|
||||||
|
*aOutVelocity = ParentLayerPoint(mDeltaNorm.x * newVelocity,
|
||||||
|
mDeltaNorm.y * newVelocity);
|
||||||
|
mLastPos = mCurrentPos;
|
||||||
|
} else {
|
||||||
|
*aOutOffset = (mTargetPos - mLastPos);
|
||||||
|
*aOutVelocity = ParentLayerPoint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidFlingPhysics::SampleImpl(const TimeDuration& aDelta,
|
||||||
|
float* aOutVelocity)
|
||||||
|
{
|
||||||
|
mDurationSoFar += aDelta;
|
||||||
|
if (mDurationSoFar >= mTargetDuration) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float timeRatio = mDurationSoFar.ToSeconds() / mTargetDuration.ToSeconds();
|
||||||
|
float distanceCoef = 1.0f;
|
||||||
|
float velocityCoef = 0.0f;
|
||||||
|
gSplineConstants->CalculateCoefficients(timeRatio, &distanceCoef, &velocityCoef);
|
||||||
|
|
||||||
|
// The caller expects the velocity in pixels per _millisecond_.
|
||||||
|
*aOutVelocity = velocityCoef * mTargetDistance / mTargetDuration.ToMilliseconds();
|
||||||
|
|
||||||
|
mCurrentPos = mTargetPos * distanceCoef;
|
||||||
|
|
||||||
|
ParentLayerPoint remainder = mTargetPos - mCurrentPos;
|
||||||
|
if (fabsf(remainder.x) < kThresholdForFlingEnd && fabsf(remainder.y) < kThresholdForFlingEnd) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace layers
|
||||||
|
} // namespace mozilla
|
||||||
46
gfx/layers/apz/src/AndroidFlingPhysics.h
Normal file
46
gfx/layers/apz/src/AndroidFlingPhysics.h
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#ifndef mozilla_layers_AndroidFlingPhysics_h_
|
||||||
|
#define mozilla_layers_AndroidFlingPhysics_h_
|
||||||
|
|
||||||
|
#include "AsyncPanZoomController.h"
|
||||||
|
#include "Units.h"
|
||||||
|
#include "gfxPrefs.h"
|
||||||
|
#include "mozilla/Assertions.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace layers {
|
||||||
|
|
||||||
|
class AndroidFlingPhysics {
|
||||||
|
public:
|
||||||
|
void Init(const ParentLayerPoint& aVelocity);
|
||||||
|
void Sample(const TimeDuration& aDelta,
|
||||||
|
ParentLayerPoint* aOutVelocity,
|
||||||
|
ParentLayerPoint* aOutOffset);
|
||||||
|
|
||||||
|
static void InitializeGlobalState();
|
||||||
|
private:
|
||||||
|
// Returns false if the animation should end.
|
||||||
|
bool SampleImpl(const TimeDuration& aDelta, float* aOutVelocity);
|
||||||
|
|
||||||
|
// Information pertaining to the current fling.
|
||||||
|
// This is initialized on each call to Init().
|
||||||
|
ParentLayerCoord mVelocity; // diagonal velocity (length of velocity vector)
|
||||||
|
TimeDuration mTargetDuration;
|
||||||
|
TimeDuration mDurationSoFar;
|
||||||
|
ParentLayerPoint mLastPos;
|
||||||
|
ParentLayerPoint mCurrentPos;
|
||||||
|
ParentLayerCoord mTargetDistance; // diagonal distance
|
||||||
|
ParentLayerPoint mTargetPos; // really a target *offset* relative to the
|
||||||
|
// start position, which we don't track
|
||||||
|
ParentLayerPoint mDeltaNorm; // mTargetPos with length normalized to 1
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace layers
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
|
#endif // mozilla_layers_AndroidFlingPhysics_h_
|
||||||
|
|
@ -50,6 +50,7 @@ struct AsyncTransform;
|
||||||
class AsyncPanZoomAnimation;
|
class AsyncPanZoomAnimation;
|
||||||
class StackScrollerFlingAnimation;
|
class StackScrollerFlingAnimation;
|
||||||
template <typename FlingPhysics> class GenericFlingAnimation;
|
template <typename FlingPhysics> class GenericFlingAnimation;
|
||||||
|
class AndroidFlingPhysics;
|
||||||
class DesktopFlingPhysics;
|
class DesktopFlingPhysics;
|
||||||
class InputBlockState;
|
class InputBlockState;
|
||||||
struct FlingHandoffState;
|
struct FlingHandoffState;
|
||||||
|
|
@ -1164,6 +1165,7 @@ private:
|
||||||
friend class StackScrollerFlingAnimation;
|
friend class StackScrollerFlingAnimation;
|
||||||
friend class AutoscrollAnimation;
|
friend class AutoscrollAnimation;
|
||||||
template <typename FlingPhysics> friend class GenericFlingAnimation;
|
template <typename FlingPhysics> friend class GenericFlingAnimation;
|
||||||
|
friend class AndroidFlingPhysics;
|
||||||
friend class DesktopFlingPhysics;
|
friend class DesktopFlingPhysics;
|
||||||
friend class OverscrollAnimation;
|
friend class OverscrollAnimation;
|
||||||
friend class SmoothScrollAnimation;
|
friend class SmoothScrollAnimation;
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
|
||||||
UNIFIED_SOURCES += [
|
UNIFIED_SOURCES += [
|
||||||
'apz/src/AndroidAPZ.cpp',
|
'apz/src/AndroidAPZ.cpp',
|
||||||
'apz/src/AndroidDynamicToolbarAnimator.cpp',
|
'apz/src/AndroidDynamicToolbarAnimator.cpp',
|
||||||
|
'apz/src/AndroidFlingPhysics.cpp',
|
||||||
]
|
]
|
||||||
EXPORTS.mozilla.layers += [
|
EXPORTS.mozilla.layers += [
|
||||||
'apz/src/AndroidDynamicToolbarAnimator.h',
|
'apz/src/AndroidDynamicToolbarAnimator.h',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue