зеркало из https://github.com/mozilla/gecko-dev.git
678 строки
24 KiB
C++
678 строки
24 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set sw=2 ts=8 et 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 "Axis.h"
|
|
#include <math.h> // for fabsf, pow, powf
|
|
#include <algorithm> // for max
|
|
#include "AsyncPanZoomController.h" // for AsyncPanZoomController
|
|
#include "mozilla/layers/APZCTreeManager.h" // for APZCTreeManager
|
|
#include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread
|
|
#include "FrameMetrics.h" // for FrameMetrics
|
|
#include "mozilla/Attributes.h" // for final
|
|
#include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction
|
|
#include "mozilla/Preferences.h" // for Preferences
|
|
#include "mozilla/gfx/Rect.h" // for RoundedIn
|
|
#include "mozilla/mozalloc.h" // for operator new
|
|
#include "mozilla/FloatingPoint.h" // for FuzzyEqualsAdditive
|
|
#include "mozilla/StaticPtr.h" // for StaticAutoPtr
|
|
#include "nsMathUtils.h" // for NS_lround
|
|
#include "nsPrintfCString.h" // for nsPrintfCString
|
|
#include "nsThreadUtils.h" // for NS_DispatchToMainThread, etc
|
|
#include "nscore.h" // for NS_IMETHOD
|
|
#include "gfxPrefs.h" // for the preferences
|
|
|
|
#define AXIS_LOG(...)
|
|
// #define AXIS_LOG(...) printf_stderr("AXIS: " __VA_ARGS__)
|
|
|
|
namespace mozilla {
|
|
namespace layers {
|
|
|
|
// When we compute the velocity we do so by taking two input events and
|
|
// dividing the distance delta over the time delta. In some cases the time
|
|
// delta can be really small, which can make the velocity computation very
|
|
// volatile. To avoid this we impose a minimum time delta below which we do
|
|
// not recompute the velocity.
|
|
const uint32_t MIN_VELOCITY_SAMPLE_TIME_MS = 5;
|
|
|
|
bool FuzzyEqualsCoordinate(float aValue1, float aValue2)
|
|
{
|
|
return FuzzyEqualsAdditive(aValue1, aValue2, COORDINATE_EPSILON)
|
|
|| FuzzyEqualsMultiplicative(aValue1, aValue2);
|
|
}
|
|
|
|
extern StaticAutoPtr<ComputedTimingFunction> gVelocityCurveFunction;
|
|
|
|
Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController)
|
|
: mPos(0),
|
|
mVelocitySampleTimeMs(0),
|
|
mVelocitySamplePos(0),
|
|
mVelocity(0.0f),
|
|
mAxisLocked(false),
|
|
mAsyncPanZoomController(aAsyncPanZoomController),
|
|
mOverscroll(0),
|
|
mFirstOverscrollAnimationSample(0),
|
|
mLastOverscrollPeak(0),
|
|
mOverscrollScale(1.0f)
|
|
{
|
|
}
|
|
|
|
float Axis::ToLocalVelocity(float aVelocityInchesPerMs) const {
|
|
ScreenPoint velocity = MakePoint(aVelocityInchesPerMs * APZCTreeManager::GetDPI());
|
|
// Use ToScreenCoordinates() to convert a point rather than a vector by
|
|
// treating the point as a vector, and using (0, 0) as the anchor.
|
|
ScreenPoint panStart = mAsyncPanZoomController->ToScreenCoordinates(
|
|
mAsyncPanZoomController->PanStart(),
|
|
ParentLayerPoint());
|
|
ParentLayerPoint localVelocity =
|
|
mAsyncPanZoomController->ToParentLayerCoordinates(velocity, panStart);
|
|
return localVelocity.Length();
|
|
}
|
|
|
|
void Axis::UpdateWithTouchAtDevicePoint(ParentLayerCoord aPos, ParentLayerCoord aAdditionalDelta, uint32_t aTimestampMs) {
|
|
// mVelocityQueue is controller-thread only
|
|
APZThreadUtils::AssertOnControllerThread();
|
|
|
|
if (aTimestampMs <= mVelocitySampleTimeMs + MIN_VELOCITY_SAMPLE_TIME_MS) {
|
|
// See also the comment on MIN_VELOCITY_SAMPLE_TIME_MS.
|
|
// We still update mPos so that the positioning is correct (and we don't run
|
|
// into problems like bug 1042734) but the velocity will remain where it was.
|
|
// In particular we don't update either mVelocitySampleTimeMs or
|
|
// mVelocitySamplePos so that eventually when we do get an event with the
|
|
// required time delta we use the corresponding distance delta as well.
|
|
AXIS_LOG("%p|%s skipping velocity computation for small time delta %dms\n",
|
|
mAsyncPanZoomController, Name(), (aTimestampMs - mVelocitySampleTimeMs));
|
|
mPos = aPos;
|
|
return;
|
|
}
|
|
|
|
float newVelocity = mAxisLocked ? 0.0f : (float)(mVelocitySamplePos - aPos + aAdditionalDelta) / (float)(aTimestampMs - mVelocitySampleTimeMs);
|
|
|
|
newVelocity = ApplyFlingCurveToVelocity(newVelocity);
|
|
|
|
AXIS_LOG("%p|%s updating velocity to %f with touch\n",
|
|
mAsyncPanZoomController, Name(), newVelocity);
|
|
mVelocity = newVelocity;
|
|
mPos = aPos;
|
|
mVelocitySampleTimeMs = aTimestampMs;
|
|
mVelocitySamplePos = aPos;
|
|
|
|
AddVelocityToQueue(aTimestampMs, mVelocity);
|
|
}
|
|
|
|
float Axis::ApplyFlingCurveToVelocity(float aVelocity) const {
|
|
float newVelocity = aVelocity;
|
|
if (gfxPrefs::APZMaxVelocity() > 0.0f) {
|
|
bool velocityIsNegative = (newVelocity < 0);
|
|
newVelocity = fabs(newVelocity);
|
|
|
|
float maxVelocity = ToLocalVelocity(gfxPrefs::APZMaxVelocity());
|
|
newVelocity = std::min(newVelocity, maxVelocity);
|
|
|
|
if (gfxPrefs::APZCurveThreshold() > 0.0f && gfxPrefs::APZCurveThreshold() < gfxPrefs::APZMaxVelocity()) {
|
|
float curveThreshold = ToLocalVelocity(gfxPrefs::APZCurveThreshold());
|
|
if (newVelocity > curveThreshold) {
|
|
// here, 0 < curveThreshold < newVelocity <= maxVelocity, so we apply the curve
|
|
float scale = maxVelocity - curveThreshold;
|
|
float funcInput = (newVelocity - curveThreshold) / scale;
|
|
float funcOutput =
|
|
gVelocityCurveFunction->GetValue(funcInput,
|
|
ComputedTimingFunction::BeforeFlag::Unset);
|
|
float curvedVelocity = (funcOutput * scale) + curveThreshold;
|
|
AXIS_LOG("%p|%s curving up velocity from %f to %f\n",
|
|
mAsyncPanZoomController, Name(), newVelocity, curvedVelocity);
|
|
newVelocity = curvedVelocity;
|
|
}
|
|
}
|
|
|
|
if (velocityIsNegative) {
|
|
newVelocity = -newVelocity;
|
|
}
|
|
}
|
|
|
|
return newVelocity;
|
|
}
|
|
|
|
void Axis::AddVelocityToQueue(uint32_t aTimestampMs, float aVelocity) {
|
|
mVelocityQueue.AppendElement(std::make_pair(aTimestampMs, aVelocity));
|
|
if (mVelocityQueue.Length() > gfxPrefs::APZMaxVelocityQueueSize()) {
|
|
mVelocityQueue.RemoveElementAt(0);
|
|
}
|
|
}
|
|
|
|
void Axis::HandleTouchVelocity(uint32_t aTimestampMs, float aSpeed) {
|
|
// mVelocityQueue is controller-thread only
|
|
APZThreadUtils::AssertOnControllerThread();
|
|
|
|
mVelocity = ApplyFlingCurveToVelocity(aSpeed);
|
|
mVelocitySampleTimeMs = aTimestampMs;
|
|
|
|
AddVelocityToQueue(aTimestampMs, mVelocity);
|
|
}
|
|
|
|
void Axis::StartTouch(ParentLayerCoord aPos, uint32_t aTimestampMs) {
|
|
mStartPos = aPos;
|
|
mPos = aPos;
|
|
mVelocitySampleTimeMs = aTimestampMs;
|
|
mVelocitySamplePos = aPos;
|
|
mAxisLocked = false;
|
|
}
|
|
|
|
bool Axis::AdjustDisplacement(ParentLayerCoord aDisplacement,
|
|
/* ParentLayerCoord */ float& aDisplacementOut,
|
|
/* ParentLayerCoord */ float& aOverscrollAmountOut,
|
|
bool aForceOverscroll /* = false */)
|
|
{
|
|
if (mAxisLocked) {
|
|
aOverscrollAmountOut = 0;
|
|
aDisplacementOut = 0;
|
|
return false;
|
|
}
|
|
if (aForceOverscroll) {
|
|
aOverscrollAmountOut = aDisplacement;
|
|
aDisplacementOut = 0;
|
|
return false;
|
|
}
|
|
|
|
EndOverscrollAnimation();
|
|
|
|
ParentLayerCoord displacement = aDisplacement;
|
|
|
|
// First consume any overscroll in the opposite direction along this axis.
|
|
ParentLayerCoord consumedOverscroll = 0;
|
|
if (mOverscroll > 0 && aDisplacement < 0) {
|
|
consumedOverscroll = std::min(mOverscroll, -aDisplacement);
|
|
} else if (mOverscroll < 0 && aDisplacement > 0) {
|
|
consumedOverscroll = 0.f - std::min(-mOverscroll, aDisplacement);
|
|
}
|
|
mOverscroll -= consumedOverscroll;
|
|
displacement += consumedOverscroll;
|
|
|
|
// Split the requested displacement into an allowed displacement that does
|
|
// not overscroll, and an overscroll amount.
|
|
aOverscrollAmountOut = DisplacementWillOverscrollAmount(displacement);
|
|
if (aOverscrollAmountOut != 0.0f) {
|
|
// No need to have a velocity along this axis anymore; it won't take us
|
|
// anywhere, so we're just spinning needlessly.
|
|
AXIS_LOG("%p|%s has overscrolled, clearing velocity\n",
|
|
mAsyncPanZoomController, Name());
|
|
mVelocity = 0.0f;
|
|
displacement -= aOverscrollAmountOut;
|
|
}
|
|
aDisplacementOut = displacement;
|
|
return fabsf(consumedOverscroll) > EPSILON;
|
|
}
|
|
|
|
ParentLayerCoord Axis::ApplyResistance(ParentLayerCoord aRequestedOverscroll) const {
|
|
// 'resistanceFactor' is a value between 0 and 1, which:
|
|
// - tends to 1 as the existing overscroll tends to 0
|
|
// - tends to 0 as the existing overscroll tends to the composition length
|
|
// The actual overscroll is the requested overscroll multiplied by this
|
|
// factor; this should prevent overscrolling by more than the composition
|
|
// length.
|
|
float resistanceFactor = 1 - fabsf(GetOverscroll()) / GetCompositionLength();
|
|
return resistanceFactor < 0 ? ParentLayerCoord(0) : aRequestedOverscroll * resistanceFactor;
|
|
}
|
|
|
|
void Axis::OverscrollBy(ParentLayerCoord aOverscroll) {
|
|
MOZ_ASSERT(CanScroll());
|
|
// We can get some spurious calls to OverscrollBy() with near-zero values
|
|
// due to rounding error. Ignore those (they might trip the asserts below.)
|
|
if (FuzzyEqualsAdditive(aOverscroll.value, 0.0f, COORDINATE_EPSILON)) {
|
|
return;
|
|
}
|
|
EndOverscrollAnimation();
|
|
aOverscroll = ApplyResistance(aOverscroll);
|
|
if (aOverscroll > 0) {
|
|
#ifdef DEBUG
|
|
if (!FuzzyEqualsCoordinate(GetCompositionEnd().value, GetPageEnd().value)) {
|
|
nsPrintfCString message("composition end (%f) is not equal (within error) to page end (%f)\n",
|
|
GetCompositionEnd().value, GetPageEnd().value);
|
|
NS_ASSERTION(false, message.get());
|
|
MOZ_CRASH("GFX: Overscroll issue > 0");
|
|
}
|
|
#endif
|
|
MOZ_ASSERT(mOverscroll >= 0);
|
|
} else if (aOverscroll < 0) {
|
|
#ifdef DEBUG
|
|
if (!FuzzyEqualsCoordinate(GetOrigin().value, GetPageStart().value)) {
|
|
nsPrintfCString message("composition origin (%f) is not equal (within error) to page origin (%f)\n",
|
|
GetOrigin().value, GetPageStart().value);
|
|
NS_ASSERTION(false, message.get());
|
|
MOZ_CRASH("GFX: Overscroll issue < 0");
|
|
}
|
|
#endif
|
|
MOZ_ASSERT(mOverscroll <= 0);
|
|
}
|
|
mOverscroll += aOverscroll;
|
|
}
|
|
|
|
ParentLayerCoord Axis::GetOverscroll() const {
|
|
ParentLayerCoord result = (mOverscroll - mLastOverscrollPeak) / mOverscrollScale;
|
|
|
|
// Assert that we return overscroll in the correct direction
|
|
#ifdef DEBUG
|
|
if ((result.value * mFirstOverscrollAnimationSample.value) < 0.0f) {
|
|
nsPrintfCString message("GetOverscroll() (%f) and first overscroll animation sample (%f) have different signs\n",
|
|
result.value, mFirstOverscrollAnimationSample.value);
|
|
NS_ASSERTION(false, message.get());
|
|
MOZ_CRASH("GFX: Overscroll issue");
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
void Axis::StartOverscrollAnimation(float aVelocity) {
|
|
// Make sure any state from a previous animation has been cleared.
|
|
MOZ_ASSERT(mFirstOverscrollAnimationSample == 0 &&
|
|
mLastOverscrollPeak == 0 &&
|
|
mOverscrollScale == 1);
|
|
|
|
SetVelocity(aVelocity);
|
|
}
|
|
|
|
void Axis::EndOverscrollAnimation() {
|
|
ParentLayerCoord overscroll = GetOverscroll();
|
|
mFirstOverscrollAnimationSample = 0;
|
|
mLastOverscrollPeak = 0;
|
|
mOverscrollScale = 1.0f;
|
|
mOverscroll = overscroll;
|
|
}
|
|
|
|
void Axis::StepOverscrollAnimation(double aStepDurationMilliseconds) {
|
|
// Apply spring physics to the overscroll as time goes on.
|
|
// Note: this method of sampling isn't perfectly smooth, as it assumes
|
|
// a constant velocity over 'aDelta', instead of an accelerating velocity.
|
|
// (The way we applying friction to flings has the same issue.)
|
|
// Hooke's law with damping:
|
|
// F = -kx - bv
|
|
// where
|
|
// k is a constant related to the stiffness of the spring
|
|
// The larger the constant, the stiffer the spring.
|
|
// x is the displacement of the end of the spring from its equilibrium
|
|
// In our scenario, it's the amount of overscroll on the axis.
|
|
// b is a constant that provides damping (friction)
|
|
// v is the velocity of the point at the end of the spring
|
|
// See http://gafferongames.com/game-physics/spring-physics/
|
|
const float kSpringStiffness = gfxPrefs::APZOverscrollSpringStiffness();
|
|
const float kSpringFriction = gfxPrefs::APZOverscrollSpringFriction();
|
|
|
|
// Apply spring force.
|
|
float springForce = -1 * kSpringStiffness * mOverscroll;
|
|
// Assume unit mass, so force = acceleration.
|
|
float oldVelocity = mVelocity;
|
|
mVelocity += springForce * aStepDurationMilliseconds;
|
|
|
|
// Apply dampening.
|
|
mVelocity *= pow(double(1 - kSpringFriction), aStepDurationMilliseconds);
|
|
AXIS_LOG("%p|%s sampled overscroll animation, leaving velocity at %f\n",
|
|
mAsyncPanZoomController, Name(), mVelocity);
|
|
|
|
// At the peak of each oscillation, record new offset and scaling factors for
|
|
// overscroll, to ensure that GetOverscroll always returns a value of the
|
|
// same sign, and that this value is correctly adjusted as the spring is
|
|
// dampened.
|
|
// To handle the case where one of the velocity samples is exaclty zero,
|
|
// consider a sign change to have occurred when the outgoing velocity is zero.
|
|
bool velocitySignChange = (oldVelocity * mVelocity) < 0 || mVelocity == 0;
|
|
if (mFirstOverscrollAnimationSample == 0.0f) {
|
|
mFirstOverscrollAnimationSample = mOverscroll;
|
|
|
|
// It's possible to start sampling overscroll with velocity == 0, or
|
|
// velocity in the opposite direction of overscroll, so make sure we
|
|
// correctly record the peak in this case.
|
|
if (mOverscroll != 0 && ((mOverscroll > 0 ? oldVelocity : -oldVelocity) <= 0.0f)) {
|
|
velocitySignChange = true;
|
|
}
|
|
}
|
|
if (velocitySignChange) {
|
|
bool oddOscillation = (mOverscroll.value * mFirstOverscrollAnimationSample.value) < 0.0f;
|
|
mLastOverscrollPeak = oddOscillation ? mOverscroll : -mOverscroll;
|
|
mOverscrollScale = 2.0f;
|
|
}
|
|
|
|
// Adjust the amount of overscroll based on the velocity.
|
|
// Note that we allow for oscillations.
|
|
mOverscroll += (mVelocity * aStepDurationMilliseconds);
|
|
|
|
// Our mechanism for translating a set of mOverscroll values that oscillate
|
|
// around zero to a set of GetOverscroll() values that have the same sign
|
|
// (so content is always stretched, never compressed) assumes that
|
|
// mOverscroll does not exceed mLastOverscrollPeak in magnitude. If our
|
|
// calculations were exact, this would be the case, as a dampened spring
|
|
// should never attain a displacement greater in magnitude than a previous
|
|
// peak. In our approximation calculations, however, this may not hold
|
|
// exactly. To ensure the assumption is not violated, we clamp the magnitude
|
|
// of mOverscroll.
|
|
if (mLastOverscrollPeak != 0 && fabs(mOverscroll) > fabs(mLastOverscrollPeak)) {
|
|
mOverscroll = (mOverscroll >= 0) ? fabs(mLastOverscrollPeak) : -fabs(mLastOverscrollPeak);
|
|
}
|
|
}
|
|
|
|
bool Axis::SampleOverscrollAnimation(const TimeDuration& aDelta) {
|
|
// Short-circuit early rather than running through all the sampling code.
|
|
if (mVelocity == 0.0f && mOverscroll == 0.0f) {
|
|
return false;
|
|
}
|
|
|
|
// We approximate the curve traced out by the velocity of the spring
|
|
// over time by breaking up the curve into small segments over which we
|
|
// consider the velocity to be constant. If the animation is sampled
|
|
// sufficiently often, then treating |aDelta| as a single segment of this
|
|
// sort would be fine, but the frequency at which the animation is sampled
|
|
// can be affected by external factors, and as the segment size grows larger,
|
|
// the approximation gets worse and the approximated curve can even diverge
|
|
// (i.e. oscillate forever, with displacements of increasing absolute value)!
|
|
// To avoid this, we break up |aDelta| into smaller segments of length 1 ms
|
|
// each, and a segment of any remaining fractional milliseconds.
|
|
double milliseconds = aDelta.ToMilliseconds();
|
|
int wholeMilliseconds = (int) aDelta.ToMilliseconds();
|
|
double fractionalMilliseconds = milliseconds - wholeMilliseconds;
|
|
for (int i = 0; i < wholeMilliseconds; ++i) {
|
|
StepOverscrollAnimation(1);
|
|
}
|
|
StepOverscrollAnimation(fractionalMilliseconds);
|
|
|
|
// If both the velocity and the displacement fall below a threshold, stop
|
|
// the animation so we don't continue doing tiny oscillations that aren't
|
|
// noticeable.
|
|
if (fabs(mOverscroll) < gfxPrefs::APZOverscrollStopDistanceThreshold() &&
|
|
fabs(mVelocity) < gfxPrefs::APZOverscrollStopVelocityThreshold()) {
|
|
// "Jump" to the at-rest state. The jump shouldn't be noticeable as the
|
|
// velocity and overscroll are already low.
|
|
AXIS_LOG("%p|%s oscillation dropped below threshold, going to rest\n",
|
|
mAsyncPanZoomController, Name());
|
|
ClearOverscroll();
|
|
mVelocity = 0;
|
|
return false;
|
|
}
|
|
|
|
// Otherwise, continue the animation.
|
|
return true;
|
|
}
|
|
|
|
bool Axis::IsOverscrolled() const {
|
|
return mOverscroll != 0.f;
|
|
}
|
|
|
|
void Axis::ClearOverscroll() {
|
|
EndOverscrollAnimation();
|
|
mOverscroll = 0;
|
|
}
|
|
|
|
ParentLayerCoord Axis::PanStart() const {
|
|
return mStartPos;
|
|
}
|
|
|
|
ParentLayerCoord Axis::PanDistance() const {
|
|
return fabs(mPos - mStartPos);
|
|
}
|
|
|
|
ParentLayerCoord Axis::PanDistance(ParentLayerCoord aPos) const {
|
|
return fabs(aPos - mStartPos);
|
|
}
|
|
|
|
void Axis::EndTouch(uint32_t aTimestampMs) {
|
|
// mVelocityQueue is controller-thread only
|
|
APZThreadUtils::AssertOnControllerThread();
|
|
|
|
mAxisLocked = false;
|
|
mVelocity = 0;
|
|
int count = 0;
|
|
while (!mVelocityQueue.IsEmpty()) {
|
|
uint32_t timeDelta = (aTimestampMs - mVelocityQueue[0].first);
|
|
if (timeDelta < gfxPrefs::APZVelocityRelevanceTime()) {
|
|
count++;
|
|
mVelocity += mVelocityQueue[0].second;
|
|
}
|
|
mVelocityQueue.RemoveElementAt(0);
|
|
}
|
|
if (count > 1) {
|
|
mVelocity /= count;
|
|
}
|
|
AXIS_LOG("%p|%s ending touch, computed velocity %f\n",
|
|
mAsyncPanZoomController, Name(), mVelocity);
|
|
}
|
|
|
|
void Axis::CancelGesture() {
|
|
// mVelocityQueue is controller-thread only
|
|
APZThreadUtils::AssertOnControllerThread();
|
|
|
|
AXIS_LOG("%p|%s cancelling touch, clearing velocity queue\n",
|
|
mAsyncPanZoomController, Name());
|
|
mVelocity = 0.0f;
|
|
while (!mVelocityQueue.IsEmpty()) {
|
|
mVelocityQueue.RemoveElementAt(0);
|
|
}
|
|
}
|
|
|
|
bool Axis::CanScroll() const {
|
|
return GetPageLength() - GetCompositionLength() > COORDINATE_EPSILON;
|
|
}
|
|
|
|
bool Axis::CanScroll(ParentLayerCoord aDelta) const
|
|
{
|
|
if (!CanScroll() || mAxisLocked) {
|
|
return false;
|
|
}
|
|
|
|
return fabs(DisplacementWillOverscrollAmount(aDelta) - aDelta) > COORDINATE_EPSILON;
|
|
}
|
|
|
|
CSSCoord Axis::ClampOriginToScrollableRect(CSSCoord aOrigin) const
|
|
{
|
|
CSSToParentLayerScale zoom = GetScaleForAxis(GetFrameMetrics().GetZoom());
|
|
ParentLayerCoord origin = aOrigin * zoom;
|
|
|
|
ParentLayerCoord result;
|
|
if (origin < GetPageStart()) {
|
|
result = GetPageStart();
|
|
} else if (origin + GetCompositionLength() > GetPageEnd()) {
|
|
result = GetPageEnd() - GetCompositionLength();
|
|
} else {
|
|
return aOrigin;
|
|
}
|
|
|
|
return result / zoom;
|
|
}
|
|
|
|
bool Axis::CanScrollNow() const {
|
|
return !mAxisLocked && CanScroll();
|
|
}
|
|
|
|
bool Axis::FlingApplyFrictionOrCancel(const TimeDuration& aDelta,
|
|
float aFriction,
|
|
float aThreshold) {
|
|
if (fabsf(mVelocity) <= aThreshold) {
|
|
// If the velocity is very low, just set it to 0 and stop the fling,
|
|
// otherwise we'll just asymptotically approach 0 and the user won't
|
|
// actually see any changes.
|
|
mVelocity = 0.0f;
|
|
return false;
|
|
} else {
|
|
mVelocity *= pow(1.0f - aFriction, float(aDelta.ToMilliseconds()));
|
|
}
|
|
AXIS_LOG("%p|%s reduced velocity to %f due to friction\n",
|
|
mAsyncPanZoomController, Name(), mVelocity);
|
|
return true;
|
|
}
|
|
|
|
ParentLayerCoord Axis::DisplacementWillOverscrollAmount(ParentLayerCoord aDisplacement) const {
|
|
ParentLayerCoord newOrigin = GetOrigin() + aDisplacement;
|
|
ParentLayerCoord newCompositionEnd = GetCompositionEnd() + aDisplacement;
|
|
// If the current pan plus a displacement takes the window to the left of or
|
|
// above the current page rect.
|
|
bool minus = newOrigin < GetPageStart();
|
|
// If the current pan plus a displacement takes the window to the right of or
|
|
// below the current page rect.
|
|
bool plus = newCompositionEnd > GetPageEnd();
|
|
if (minus && plus) {
|
|
// Don't handle overscrolled in both directions; a displacement can't cause
|
|
// this, it must have already been zoomed out too far.
|
|
return 0;
|
|
}
|
|
if (minus) {
|
|
return newOrigin - GetPageStart();
|
|
}
|
|
if (plus) {
|
|
return newCompositionEnd - GetPageEnd();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
CSSCoord Axis::ScaleWillOverscrollAmount(float aScale, CSSCoord aFocus) const {
|
|
// Internally, do computations in ParentLayer coordinates *before* the scale
|
|
// is applied.
|
|
CSSToParentLayerScale zoom = GetFrameMetrics().GetZoom().ToScaleFactor();
|
|
ParentLayerCoord focus = aFocus * zoom;
|
|
ParentLayerCoord originAfterScale = (GetOrigin() + focus) - (focus / aScale);
|
|
|
|
bool both = ScaleWillOverscrollBothSides(aScale);
|
|
bool minus = GetPageStart() - originAfterScale > COORDINATE_EPSILON;
|
|
bool plus = (originAfterScale + (GetCompositionLength() / aScale)) - GetPageEnd() > COORDINATE_EPSILON;
|
|
|
|
if ((minus && plus) || both) {
|
|
// If we ever reach here it's a bug in the client code.
|
|
MOZ_ASSERT(false, "In an OVERSCROLL_BOTH condition in ScaleWillOverscrollAmount");
|
|
return 0;
|
|
}
|
|
if (minus) {
|
|
return (originAfterScale - GetPageStart()) / zoom;
|
|
}
|
|
if (plus) {
|
|
return (originAfterScale + (GetCompositionLength() / aScale) - GetPageEnd()) / zoom;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool Axis::IsAxisLocked() const {
|
|
return mAxisLocked;
|
|
}
|
|
|
|
float Axis::GetVelocity() const {
|
|
return mAxisLocked ? 0 : mVelocity;
|
|
}
|
|
|
|
void Axis::SetVelocity(float aVelocity) {
|
|
AXIS_LOG("%p|%s direct-setting velocity to %f\n",
|
|
mAsyncPanZoomController, Name(), aVelocity);
|
|
mVelocity = aVelocity;
|
|
}
|
|
|
|
ParentLayerCoord Axis::GetCompositionEnd() const {
|
|
return GetOrigin() + GetCompositionLength();
|
|
}
|
|
|
|
ParentLayerCoord Axis::GetPageEnd() const {
|
|
return GetPageStart() + GetPageLength();
|
|
}
|
|
|
|
ParentLayerCoord Axis::GetOrigin() const {
|
|
ParentLayerPoint origin = GetFrameMetrics().GetScrollOffset() * GetFrameMetrics().GetZoom();
|
|
return GetPointOffset(origin);
|
|
}
|
|
|
|
ParentLayerCoord Axis::GetCompositionLength() const {
|
|
return GetRectLength(GetFrameMetrics().GetCompositionBounds());
|
|
}
|
|
|
|
ParentLayerCoord Axis::GetPageStart() const {
|
|
ParentLayerRect pageRect = GetFrameMetrics().GetExpandedScrollableRect() * GetFrameMetrics().GetZoom();
|
|
return GetRectOffset(pageRect);
|
|
}
|
|
|
|
ParentLayerCoord Axis::GetPageLength() const {
|
|
ParentLayerRect pageRect = GetFrameMetrics().GetExpandedScrollableRect() * GetFrameMetrics().GetZoom();
|
|
return GetRectLength(pageRect);
|
|
}
|
|
|
|
bool Axis::ScaleWillOverscrollBothSides(float aScale) const {
|
|
const FrameMetrics& metrics = GetFrameMetrics();
|
|
ParentLayerRect screenCompositionBounds = metrics.GetCompositionBounds()
|
|
/ ParentLayerToParentLayerScale(aScale);
|
|
return GetRectLength(screenCompositionBounds) - GetPageLength() > COORDINATE_EPSILON;
|
|
}
|
|
|
|
const FrameMetrics& Axis::GetFrameMetrics() const {
|
|
return mAsyncPanZoomController->GetFrameMetrics();
|
|
}
|
|
|
|
|
|
AxisX::AxisX(AsyncPanZoomController* aAsyncPanZoomController)
|
|
: Axis(aAsyncPanZoomController)
|
|
{
|
|
|
|
}
|
|
|
|
ParentLayerCoord AxisX::GetPointOffset(const ParentLayerPoint& aPoint) const
|
|
{
|
|
return aPoint.x;
|
|
}
|
|
|
|
ParentLayerCoord AxisX::GetRectLength(const ParentLayerRect& aRect) const
|
|
{
|
|
return aRect.width;
|
|
}
|
|
|
|
ParentLayerCoord AxisX::GetRectOffset(const ParentLayerRect& aRect) const
|
|
{
|
|
return aRect.x;
|
|
}
|
|
|
|
CSSToParentLayerScale AxisX::GetScaleForAxis(const CSSToParentLayerScale2D& aScale) const
|
|
{
|
|
return CSSToParentLayerScale(aScale.xScale);
|
|
}
|
|
|
|
ScreenPoint AxisX::MakePoint(ScreenCoord aCoord) const
|
|
{
|
|
return ScreenPoint(aCoord, 0);
|
|
}
|
|
|
|
const char* AxisX::Name() const
|
|
{
|
|
return "X";
|
|
}
|
|
|
|
AxisY::AxisY(AsyncPanZoomController* aAsyncPanZoomController)
|
|
: Axis(aAsyncPanZoomController)
|
|
{
|
|
|
|
}
|
|
|
|
ParentLayerCoord AxisY::GetPointOffset(const ParentLayerPoint& aPoint) const
|
|
{
|
|
return aPoint.y;
|
|
}
|
|
|
|
ParentLayerCoord AxisY::GetRectLength(const ParentLayerRect& aRect) const
|
|
{
|
|
return aRect.height;
|
|
}
|
|
|
|
ParentLayerCoord AxisY::GetRectOffset(const ParentLayerRect& aRect) const
|
|
{
|
|
return aRect.y;
|
|
}
|
|
|
|
CSSToParentLayerScale AxisY::GetScaleForAxis(const CSSToParentLayerScale2D& aScale) const
|
|
{
|
|
return CSSToParentLayerScale(aScale.yScale);
|
|
}
|
|
|
|
ScreenPoint AxisY::MakePoint(ScreenCoord aCoord) const
|
|
{
|
|
return ScreenPoint(0, aCoord);
|
|
}
|
|
|
|
const char* AxisY::Name() const
|
|
{
|
|
return "Y";
|
|
}
|
|
|
|
} // namespace layers
|
|
} // namespace mozilla
|