Factor the guts of AsyncScroll into a base helper class. (bug 1139220 part 2, r=kgilbert)

This commit is contained in:
David Anderson 2015-04-01 23:17:22 -07:00
Родитель 8c72d87fb0
Коммит 0dccb5ef39
4 изменённых файлов: 281 добавлений и 187 удалений

Просмотреть файл

@ -0,0 +1,135 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "AsyncScrollBase.h"
using namespace mozilla;
AsyncScrollBase::AsyncScrollBase(nsPoint aStartPos)
: mIsFirstIteration(true)
, mStartPos(aStartPos)
{
}
void
AsyncScrollBase::Update(TimeStamp aTime,
nsPoint aDestination,
const nsSize& aCurrentVelocity)
{
TimeDuration duration = ComputeDuration(aTime);
nsSize currentVelocity = aCurrentVelocity;
if (!mIsFirstIteration) {
// If an additional event has not changed the destination, then do not let
// another minimum duration reset slow things down. If it would then
// instead continue with the existing timing function.
if (aDestination == mDestination &&
aTime + duration > mStartTime + mDuration)
{
return;
}
currentVelocity = VelocityAt(aTime);
mStartPos = PositionAt(aTime);
}
mStartTime = aTime;
mDuration = duration;
mDestination = aDestination;
InitTimingFunction(mTimingFunctionX, mStartPos.x, currentVelocity.width,
aDestination.x);
InitTimingFunction(mTimingFunctionY, mStartPos.y, currentVelocity.height,
aDestination.y);
mIsFirstIteration = false;
}
TimeDuration
AsyncScrollBase::ComputeDuration(TimeStamp aTime)
{
// Average last 3 delta durations (rounding errors up to 2ms are negligible for us)
int32_t eventsDeltaMs = (aTime - mPrevEventTime[2]).ToMilliseconds() / 3;
mPrevEventTime[2] = mPrevEventTime[1];
mPrevEventTime[1] = mPrevEventTime[0];
mPrevEventTime[0] = aTime;
// Modulate duration according to events rate (quicker events -> shorter durations).
// The desired effect is to use longer duration when scrolling slowly, such that
// it's easier to follow, but reduce the duration to make it feel more snappy when
// scrolling quickly. To reduce fluctuations of the duration, we average event
// intervals using the recent 4 timestamps (now + three prev -> 3 intervals).
int32_t durationMS = clamped<int32_t>(eventsDeltaMs * mIntervalRatio, mOriginMinMS, mOriginMaxMS);
return TimeDuration::FromMilliseconds(durationMS);
}
void
AsyncScrollBase::InitializeHistory(TimeStamp aTime)
{
// Starting a new scroll (i.e. not when extending an existing scroll animation),
// create imaginary prev timestamps with maximum relevant intervals between them.
// Longest relevant interval (which results in maximum duration)
TimeDuration maxDelta = TimeDuration::FromMilliseconds(mOriginMaxMS / mIntervalRatio);
mPrevEventTime[0] = aTime - maxDelta;
mPrevEventTime[1] = mPrevEventTime[0] - maxDelta;
mPrevEventTime[2] = mPrevEventTime[1] - maxDelta;
}
const double kCurrentVelocityWeighting = 0.25;
const double kStopDecelerationWeighting = 0.4;
void
AsyncScrollBase::InitTimingFunction(nsSMILKeySpline& aTimingFunction,
nscoord aCurrentPos,
nscoord aCurrentVelocity,
nscoord aDestination)
{
if (aDestination == aCurrentPos || kCurrentVelocityWeighting == 0) {
aTimingFunction.Init(0, 0, 1 - kStopDecelerationWeighting, 1);
return;
}
const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
double slope = aCurrentVelocity * (mDuration / oneSecond) / (aDestination - aCurrentPos);
double normalization = sqrt(1.0 + slope * slope);
double dt = 1.0 / normalization * kCurrentVelocityWeighting;
double dxy = slope / normalization * kCurrentVelocityWeighting;
aTimingFunction.Init(dt, dxy, 1 - kStopDecelerationWeighting, 1);
}
nsPoint
AsyncScrollBase::PositionAt(TimeStamp aTime) const
{
double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime));
double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime));
return nsPoint(NSToCoordRound((1 - progressX) * mStartPos.x + progressX * mDestination.x),
NSToCoordRound((1 - progressY) * mStartPos.y + progressY * mDestination.y));
}
nsSize
AsyncScrollBase::VelocityAt(TimeStamp aTime) const
{
double timeProgress = ProgressAt(aTime);
return nsSize(VelocityComponent(timeProgress, mTimingFunctionX,
mStartPos.x, mDestination.x),
VelocityComponent(timeProgress, mTimingFunctionY,
mStartPos.y, mDestination.y));
}
nscoord
AsyncScrollBase::VelocityComponent(double aTimeProgress,
const nsSMILKeySpline& aTimingFunction,
nscoord aStart,
nscoord aDestination) const
{
double dt, dxy;
aTimingFunction.GetSplineDerivativeValues(aTimeProgress, dt, dxy);
if (dt == 0)
return dxy >= 0 ? nscoord_MAX : nscoord_MIN;
const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
double slope = dxy / dt;
return NSToCoordRound(slope * (aDestination - aStart) / (mDuration / oneSecond));
}

Просмотреть файл

@ -0,0 +1,88 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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_layout_AsyncScrollBase_h_
#define mozilla_layout_AsyncScrollBase_h_
#include "mozilla/TimeStamp.h"
#include "nsPoint.h"
#include "nsSMILKeySpline.h"
namespace mozilla {
// This is the base class for driving scroll wheel animation on both the
// compositor and main thread.
class AsyncScrollBase
{
public:
typedef mozilla::TimeStamp TimeStamp;
typedef mozilla::TimeDuration TimeDuration;
AsyncScrollBase(nsPoint aStartPos);
void Update(TimeStamp aTime,
nsPoint aDestination,
const nsSize& aCurrentVelocity);
// Get the velocity at a point in time in nscoords/sec.
nsSize VelocityAt(TimeStamp aTime) const;
// Returns the expected scroll position at a given point in time, in app
// units, relative to the scroll frame.
nsPoint PositionAt(TimeStamp aTime) const;
protected:
double ProgressAt(TimeStamp aTime) const {
return clamped((aTime - mStartTime) / mDuration, 0.0, 1.0);
}
nscoord VelocityComponent(double aTimeProgress,
const nsSMILKeySpline& aTimingFunction,
nscoord aStart, nscoord aDestination) const;
// Calculate duration, possibly dynamically according to events rate and
// event origin. (also maintain previous timestamps - which are only used
// here).
TimeDuration ComputeDuration(TimeStamp aTime);
// Initialize event history.
void InitializeHistory(TimeStamp aTime);
// Initializes the timing function in such a way that the current velocity is
// preserved.
void InitTimingFunction(nsSMILKeySpline& aTimingFunction,
nscoord aCurrentPos, nscoord aCurrentVelocity,
nscoord aDestination);
// mPrevEventTime holds previous 3 timestamps for intervals averaging (to
// reduce duration fluctuations). When AsyncScroll is constructed and no
// previous timestamps are available (indicated with mIsFirstIteration),
// initialize mPrevEventTime using imaginary previous timestamps with maximum
// relevant intervals between them.
TimeStamp mPrevEventTime[3];
bool mIsFirstIteration;
TimeStamp mStartTime;
// Cached Preferences value.
//
// These values are minimum and maximum animation duration per event origin,
// and a global ratio which defines how longer is the animation's duration
// compared to the average recent events intervals (such that for a relatively
// consistent events rate, the next event arrives before current animation ends)
int32_t mOriginMinMS;
int32_t mOriginMaxMS;
double mIntervalRatio;
nsPoint mStartPos;
TimeDuration mDuration;
nsPoint mDestination;
nsSMILKeySpline mTimingFunctionX;
nsSMILKeySpline mTimingFunctionY;
};
}
#endif // mozilla_layout_AsyncScrollBase_h_

Просмотреть файл

@ -61,6 +61,7 @@ with Files('nsVideoFrame.*'):
BUG_COMPONENT = ('Core', 'Video/Audio')
EXPORTS += [
'AsyncScrollBase.h',
'nsCanvasFrame.h',
'nsContainerFrame.h',
'nsDirection.h',
@ -104,6 +105,7 @@ EXPORTS.mozilla.layout += [
]
UNIFIED_SOURCES += [
'AsyncScrollBase.cpp',
'FrameChildList.cpp',
'MathMLTextRunFactory.cpp',
'nsAbsoluteContainingBlock.cpp',

Просмотреть файл

@ -55,6 +55,7 @@
#include "nsIFrameInlines.h"
#include "gfxPlatform.h"
#include "gfxPrefs.h"
#include "AsyncScrollBase.h"
#include <mozilla/layers/AxisPhysicsModel.h>
#include <mozilla/layers/AxisPhysicsMSDModel.h>
#include <algorithm>
@ -1447,9 +1448,6 @@ NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
#define SMOOTH_SCROLL_PREF_NAME "general.smoothScroll"
const double kCurrentVelocityWeighting = 0.25;
const double kStopDecelerationWeighting = 0.4;
// AsyncSmoothMSDScroll has ref counting.
class ScrollFrameHelper::AsyncSmoothMSDScroll final : public nsARefreshObserver {
public:
@ -1581,14 +1579,16 @@ private:
};
// AsyncScroll has ref counting.
class ScrollFrameHelper::AsyncScroll final : public nsARefreshObserver {
class ScrollFrameHelper::AsyncScroll final
: public nsARefreshObserver,
public AsyncScrollBase
{
public:
typedef mozilla::TimeStamp TimeStamp;
typedef mozilla::TimeDuration TimeDuration;
explicit AsyncScroll(nsPoint aStartPos)
: mIsFirstIteration(true)
, mStartPos(aStartPos)
: AsyncScrollBase(aStartPos)
, mCallee(nullptr)
{}
@ -1599,9 +1599,6 @@ private:
}
public:
nsPoint PositionAt(TimeStamp aTime);
nsSize VelocityAt(TimeStamp aTime); // In nscoords per second
void InitSmoothScroll(TimeStamp aTime, nsPoint aDestination,
nsIAtom *aOrigin, const nsRect& aRange,
const nsSize& aCurrentVelocity);
@ -1613,53 +1610,15 @@ public:
return aTime > mStartTime + mDuration; // XXX or if we've hit the wall
}
TimeStamp mStartTime;
// mPrevEventTime holds previous 3 timestamps for intervals averaging (to
// reduce duration fluctuations). When AsyncScroll is constructed and no
// previous timestamps are available (indicated with mIsFirstIteration),
// initialize mPrevEventTime using imaginary previous timestamps with maximum
// relevant intervals between them.
TimeStamp mPrevEventTime[3];
bool mIsFirstIteration;
// Cached Preferences values to avoid re-reading them when extending an existing
// animation for the same event origin (can be as frequent as every 10(!)ms for
// a quick roll of the mouse wheel).
// These values are minimum and maximum animation duration per event origin,
// and a global ratio which defines how longer is the animation's duration
// compared to the average recent events intervals (such that for a relatively
// consistent events rate, the next event arrives before current animation ends)
// Most recent scroll origin.
nsCOMPtr<nsIAtom> mOrigin;
int32_t mOriginMinMS;
int32_t mOriginMaxMS;
double mIntervalRatio;
TimeDuration mDuration;
nsPoint mStartPos;
nsPoint mDestination;
// Allowed destination positions around mDestination
nsRect mRange;
nsSMILKeySpline mTimingFunctionX;
nsSMILKeySpline mTimingFunctionY;
bool mIsSmoothScroll;
protected:
double ProgressAt(TimeStamp aTime) {
return clamped((aTime - mStartTime) / mDuration, 0.0, 1.0);
}
nscoord VelocityComponent(double aTimeProgress,
nsSMILKeySpline& aTimingFunction,
nscoord aStart, nscoord aDestination);
// Initializes the timing function in such a way that the current velocity is
// preserved.
void InitTimingFunction(nsSMILKeySpline& aTimingFunction,
nscoord aCurrentPos, nscoord aCurrentVelocity,
nscoord aDestination);
TimeDuration CalcDurationForEventTime(TimeStamp aTime, nsIAtom *aOrigin);
private:
void InitPreferences(TimeStamp aTime, nsIAtom *aOrigin);
// The next section is observer/callback management
// Bodies of WillRefresh and RefreshDriver contain ScrollFrameHelper specific code.
@ -1707,162 +1666,72 @@ private:
}
};
nsPoint
ScrollFrameHelper::AsyncScroll::PositionAt(TimeStamp aTime) {
double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime));
double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime));
return nsPoint(NSToCoordRound((1 - progressX) * mStartPos.x + progressX * mDestination.x),
NSToCoordRound((1 - progressY) * mStartPos.y + progressY * mDestination.y));
}
nsSize
ScrollFrameHelper::AsyncScroll::VelocityAt(TimeStamp aTime) {
double timeProgress = ProgressAt(aTime);
return nsSize(VelocityComponent(timeProgress, mTimingFunctionX,
mStartPos.x, mDestination.x),
VelocityComponent(timeProgress, mTimingFunctionY,
mStartPos.y, mDestination.y));
}
/*
* Calculate duration, possibly dynamically according to events rate and event origin.
* (also maintain previous timestamps - which are only used here).
*/
TimeDuration
ScrollFrameHelper::
AsyncScroll::CalcDurationForEventTime(TimeStamp aTime, nsIAtom *aOrigin) {
void
ScrollFrameHelper::AsyncScroll::InitPreferences(TimeStamp aTime, nsIAtom *aOrigin)
{
if (!aOrigin){
aOrigin = nsGkAtoms::other;
}
// Read preferences only on first iteration or for a different event origin.
if (mIsFirstIteration || aOrigin != mOrigin) {
mOrigin = aOrigin;
mOriginMinMS = mOriginMaxMS = 0;
bool isOriginSmoothnessEnabled = false;
mIntervalRatio = 1;
// Default values for all preferences are defined in all.js
static const int32_t kDefaultMinMS = 150, kDefaultMaxMS = 150;
static const bool kDefaultIsSmoothEnabled = true;
nsAutoCString originName;
aOrigin->ToUTF8String(originName);
nsAutoCString prefBase = NS_LITERAL_CSTRING("general.smoothScroll.") + originName;
isOriginSmoothnessEnabled = Preferences::GetBool(prefBase.get(), kDefaultIsSmoothEnabled);
if (isOriginSmoothnessEnabled) {
nsAutoCString prefMin = prefBase + NS_LITERAL_CSTRING(".durationMinMS");
nsAutoCString prefMax = prefBase + NS_LITERAL_CSTRING(".durationMaxMS");
mOriginMinMS = Preferences::GetInt(prefMin.get(), kDefaultMinMS);
mOriginMaxMS = Preferences::GetInt(prefMax.get(), kDefaultMaxMS);
static const int32_t kSmoothScrollMaxAllowedAnimationDurationMS = 10000;
mOriginMaxMS = clamped(mOriginMaxMS, 0, kSmoothScrollMaxAllowedAnimationDurationMS);
mOriginMinMS = clamped(mOriginMinMS, 0, mOriginMaxMS);
}
// Keep the animation duration longer than the average event intervals
// (to "connect" consecutive scroll animations before the scroll comes to a stop).
static const double kDefaultDurationToIntervalRatio = 2; // Duplicated at all.js
mIntervalRatio = Preferences::GetInt("general.smoothScroll.durationToIntervalRatio",
kDefaultDurationToIntervalRatio * 100) / 100.0;
// Duration should be at least as long as the intervals -> ratio is at least 1
mIntervalRatio = std::max(1.0, mIntervalRatio);
if (mIsFirstIteration) {
// Starting a new scroll (i.e. not when extending an existing scroll animation),
// create imaginary prev timestamps with maximum relevant intervals between them.
// Longest relevant interval (which results in maximum duration)
TimeDuration maxDelta = TimeDuration::FromMilliseconds(mOriginMaxMS / mIntervalRatio);
mPrevEventTime[0] = aTime - maxDelta;
mPrevEventTime[1] = mPrevEventTime[0] - maxDelta;
mPrevEventTime[2] = mPrevEventTime[1] - maxDelta;
}
if (!mIsFirstIteration && aOrigin == mOrigin) {
return;
}
// Average last 3 delta durations (rounding errors up to 2ms are negligible for us)
int32_t eventsDeltaMs = (aTime - mPrevEventTime[2]).ToMilliseconds() / 3;
mPrevEventTime[2] = mPrevEventTime[1];
mPrevEventTime[1] = mPrevEventTime[0];
mPrevEventTime[0] = aTime;
mOrigin = aOrigin;
mOriginMinMS = mOriginMaxMS = 0;
bool isOriginSmoothnessEnabled = false;
mIntervalRatio = 1;
// Modulate duration according to events rate (quicker events -> shorter durations).
// The desired effect is to use longer duration when scrolling slowly, such that
// it's easier to follow, but reduce the duration to make it feel more snappy when
// scrolling quickly. To reduce fluctuations of the duration, we average event
// intervals using the recent 4 timestamps (now + three prev -> 3 intervals).
int32_t durationMS = clamped<int32_t>(eventsDeltaMs * mIntervalRatio, mOriginMinMS, mOriginMaxMS);
// Default values for all preferences are defined in all.js
static const int32_t kDefaultMinMS = 150, kDefaultMaxMS = 150;
static const bool kDefaultIsSmoothEnabled = true;
return TimeDuration::FromMilliseconds(durationMS);
nsAutoCString originName;
aOrigin->ToUTF8String(originName);
nsAutoCString prefBase = NS_LITERAL_CSTRING("general.smoothScroll.") + originName;
isOriginSmoothnessEnabled = Preferences::GetBool(prefBase.get(), kDefaultIsSmoothEnabled);
if (isOriginSmoothnessEnabled) {
nsAutoCString prefMin = prefBase + NS_LITERAL_CSTRING(".durationMinMS");
nsAutoCString prefMax = prefBase + NS_LITERAL_CSTRING(".durationMaxMS");
mOriginMinMS = Preferences::GetInt(prefMin.get(), kDefaultMinMS);
mOriginMaxMS = Preferences::GetInt(prefMax.get(), kDefaultMaxMS);
static const int32_t kSmoothScrollMaxAllowedAnimationDurationMS = 10000;
mOriginMaxMS = clamped(mOriginMaxMS, 0, kSmoothScrollMaxAllowedAnimationDurationMS);
mOriginMinMS = clamped(mOriginMinMS, 0, mOriginMaxMS);
}
// Keep the animation duration longer than the average event intervals
// (to "connect" consecutive scroll animations before the scroll comes to a stop).
static const double kDefaultDurationToIntervalRatio = 2; // Duplicated at all.js
mIntervalRatio = Preferences::GetInt("general.smoothScroll.durationToIntervalRatio",
kDefaultDurationToIntervalRatio * 100) / 100.0;
// Duration should be at least as long as the intervals -> ratio is at least 1
mIntervalRatio = std::max(1.0, mIntervalRatio);
if (mIsFirstIteration) {
InitializeHistory(aTime);
}
}
void
ScrollFrameHelper::AsyncScroll::InitSmoothScroll(TimeStamp aTime,
nsPoint aDestination,
nsIAtom *aOrigin,
const nsRect& aRange,
const nsSize& aCurrentVelocity) {
nsPoint aDestination,
nsIAtom *aOrigin,
const nsRect& aRange,
const nsSize& aCurrentVelocity)
{
InitPreferences(aTime, aOrigin);
mRange = aRange;
TimeDuration duration = CalcDurationForEventTime(aTime, aOrigin);
nsSize currentVelocity = aCurrentVelocity;
if (!mIsFirstIteration) {
// If an additional event has not changed the destination, then do not let
// another minimum duration reset slow things down. If it would then
// instead continue with the existing timing function.
if (aDestination == mDestination &&
aTime + duration > mStartTime + mDuration)
return;
currentVelocity = VelocityAt(aTime);
mStartPos = PositionAt(aTime);
}
mStartTime = aTime;
mDuration = duration;
mDestination = aDestination;
InitTimingFunction(mTimingFunctionX, mStartPos.x, currentVelocity.width,
aDestination.x);
InitTimingFunction(mTimingFunctionY, mStartPos.y, currentVelocity.height,
aDestination.y);
mIsFirstIteration = false;
}
nscoord
ScrollFrameHelper::AsyncScroll::VelocityComponent(double aTimeProgress,
nsSMILKeySpline& aTimingFunction,
nscoord aStart,
nscoord aDestination)
{
double dt, dxy;
aTimingFunction.GetSplineDerivativeValues(aTimeProgress, dt, dxy);
if (dt == 0)
return dxy >= 0 ? nscoord_MAX : nscoord_MIN;
const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
double slope = dxy / dt;
return NSToCoordRound(slope * (aDestination - aStart) / (mDuration / oneSecond));
}
void
ScrollFrameHelper::AsyncScroll::InitTimingFunction(nsSMILKeySpline& aTimingFunction,
nscoord aCurrentPos,
nscoord aCurrentVelocity,
nscoord aDestination)
{
if (aDestination == aCurrentPos || kCurrentVelocityWeighting == 0) {
aTimingFunction.Init(0, 0, 1 - kStopDecelerationWeighting, 1);
return;
}
const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
double slope = aCurrentVelocity * (mDuration / oneSecond) / (aDestination - aCurrentPos);
double normalization = sqrt(1.0 + slope * slope);
double dt = 1.0 / normalization * kCurrentVelocityWeighting;
double dxy = slope / normalization * kCurrentVelocityWeighting;
aTimingFunction.Init(dt, dxy, 1 - kStopDecelerationWeighting, 1);
Update(aTime, aDestination, aCurrentVelocity);
}
bool