diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/layers/apz/src/Axis.cpp | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/gfx/layers/apz/src/Axis.cpp b/gfx/layers/apz/src/Axis.cpp new file mode 100644 index 0000000000..c880426d3a --- /dev/null +++ b/gfx/layers/apz/src/Axis.cpp @@ -0,0 +1,525 @@ +/* -*- 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 "Axis.h" + +#include <math.h> // for fabsf, pow, powf +#include <algorithm> // for max + +#include "APZCTreeManager.h" // for APZCTreeManager +#include "AsyncPanZoomController.h" // for AsyncPanZoomController +#include "FrameMetrics.h" // for FrameMetrics +#include "SimpleVelocityTracker.h" // for FrameMetrics +#include "mozilla/Attributes.h" // for final +#include "mozilla/Preferences.h" // for Preferences +#include "mozilla/gfx/Rect.h" // for RoundedIn +#include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread +#include "mozilla/mozalloc.h" // for operator new +#include "mozilla/FloatingPoint.h" // for FuzzyEqualsAdditive +#include "nsMathUtils.h" // for NS_lround +#include "nsPrintfCString.h" // for nsPrintfCString +#include "nsThreadUtils.h" // for NS_DispatchToMainThread, etc +#include "nscore.h" // for NS_IMETHOD + +static mozilla::LazyLogModule sApzAxsLog("apz.axis"); +#define AXIS_LOG(...) MOZ_LOG(sApzAxsLog, LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { +namespace layers { + +bool FuzzyEqualsCoordinate(float aValue1, float aValue2) { + return FuzzyEqualsAdditive(aValue1, aValue2, COORDINATE_EPSILON) || + FuzzyEqualsMultiplicative(aValue1, aValue2); +} + +Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController) + : mPos(0), + mVelocity(0.0f, "Axis::mVelocity"), + mAxisLocked(false), + mAsyncPanZoomController(aAsyncPanZoomController), + mOverscroll(0), + mMSDModel(0.0, 0.0, 0.0, 400.0, 1.2), + mVelocityTracker(mAsyncPanZoomController->GetPlatformSpecificState() + ->CreateVelocityTracker(this)) {} + +float Axis::ToLocalVelocity(float aVelocityInchesPerMs) const { + ScreenPoint velocity = + MakePoint(aVelocityInchesPerMs * mAsyncPanZoomController->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, + TimeStamp aTimestamp) { + // mVelocityTracker is controller-thread only + APZThreadUtils::AssertOnControllerThread(); + + mPos = aPos; + + AXIS_LOG("%p|%s got position %f\n", mAsyncPanZoomController, Name(), + mPos.value); + if (Maybe<float> newVelocity = + mVelocityTracker->AddPosition(aPos, aTimestamp)) { + DoSetVelocity(mAxisLocked ? 0 : *newVelocity); + AXIS_LOG("%p|%s velocity from tracker is %f%s\n", mAsyncPanZoomController, + Name(), *newVelocity, + mAxisLocked ? ", but we are axis locked" : ""); + } +} + +void Axis::StartTouch(ParentLayerCoord aPos, TimeStamp aTimestamp) { + mStartPos = aPos; + mPos = aPos; + mVelocityTracker->StartTracking(aPos, aTimestamp); + 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()); + DoSetVelocity(0.0f); + displacement -= aOverscrollAmountOut; + } + aDisplacementOut = displacement; + return fabsf(consumedOverscroll) > EPSILON; +} + +ParentLayerCoord Axis::ApplyResistance( + ParentLayerCoord aRequestedOverscroll) const { + // 'resistanceFactor' is a value between 0 and 1/16, which: + // - tends to 1/16 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. + float resistanceFactor = + (1 - fabsf(GetOverscroll()) / GetCompositionLength()) / 16; + float result = resistanceFactor < 0 ? ParentLayerCoord(0) + : aRequestedOverscroll * resistanceFactor; + result = clamped(result, -8.0f, 8.0f); + return result; +} + +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 { return mOverscroll; } + +void Axis::StartOverscrollAnimation(float aVelocity) { + aVelocity = clamped(aVelocity / 2.0f, -20.0f, 20.0f); + SetVelocity(aVelocity); + mMSDModel.SetPosition(mOverscroll); + // Convert velocity from ParentLayerCoords/millisecond to + // ParentLayerCoords/second. + mMSDModel.SetVelocity(DoGetVelocity() * 1000.0); +} + +void Axis::EndOverscrollAnimation() { + mMSDModel.SetPosition(0.0); + mMSDModel.SetVelocity(0.0); +} + +bool Axis::SampleOverscrollAnimation(const TimeDuration& aDelta) { + mMSDModel.Simulate(aDelta); + mOverscroll = mMSDModel.GetPosition(); + + if (mMSDModel.IsFinished(1.0)) { + // "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(); + DoSetVelocity(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(TimeStamp aTimestamp) { + // mVelocityQueue is controller-thread only + APZThreadUtils::AssertOnControllerThread(); + + // If the velocity tracker wasn't able to compute a velocity, zero out + // the velocity to make sure we don't get a fling based on some old and + // no-longer-relevant value of mVelocity. Also if the axis is locked then + // just reset the velocity to 0 since we don't need any velocity to carry + // into the fling. + if (mAxisLocked) { + DoSetVelocity(0); + } else if (Maybe<float> velocity = + mVelocityTracker->ComputeVelocity(aTimestamp)) { + DoSetVelocity(*velocity); + } else { + DoSetVelocity(0); + } + mAxisLocked = false; + AXIS_LOG("%p|%s ending touch, computed velocity %f\n", + mAsyncPanZoomController, Name(), DoGetVelocity()); +} + +void Axis::CancelGesture() { + // mVelocityQueue is controller-thread only + APZThreadUtils::AssertOnControllerThread(); + + AXIS_LOG("%p|%s cancelling touch, clearing velocity queue\n", + mAsyncPanZoomController, Name()); + DoSetVelocity(0.0f); + mVelocityTracker->Clear(); +} + +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; + } + if (zoom == CSSToParentLayerScale(0)) { + return aOrigin; + } + return result / zoom; +} + +bool Axis::CanScrollNow() const { return !mAxisLocked && CanScroll(); } + +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 && zoom != CSSToParentLayerScale(0)) { + return (originAfterScale - GetPageStart()) / zoom; + } + if (plus && zoom != CSSToParentLayerScale(0)) { + return (originAfterScale + (GetCompositionLength() / aScale) - + GetPageEnd()) / + zoom; + } + return 0; +} + +bool Axis::IsAxisLocked() const { return mAxisLocked; } + +float Axis::GetVelocity() const { return mAxisLocked ? 0 : DoGetVelocity(); } + +void Axis::SetVelocity(float aVelocity) { + AXIS_LOG("%p|%s direct-setting velocity to %f\n", mAsyncPanZoomController, + Name(), aVelocity); + DoSetVelocity(aVelocity); +} + +ParentLayerCoord Axis::GetCompositionEnd() const { + return GetOrigin() + GetCompositionLength(); +} + +ParentLayerCoord Axis::GetPageEnd() const { + return GetPageStart() + GetPageLength(); +} + +ParentLayerCoord Axis::GetScrollRangeEnd() const { + return GetPageEnd() - GetCompositionLength(); +} + +ParentLayerCoord Axis::GetOrigin() const { + ParentLayerPoint origin = + GetFrameMetrics().GetVisualScrollOffset() * 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; +} + +float Axis::DoGetVelocity() const { + auto velocity = mVelocity.Lock(); + return velocity.ref(); +} +void Axis::DoSetVelocity(float aVelocity) { + auto velocity = mVelocity.Lock(); + velocity.ref() = aVelocity; +} + +const FrameMetrics& Axis::GetFrameMetrics() const { + return mAsyncPanZoomController->GetFrameMetrics(); +} + +const ScrollMetadata& Axis::GetScrollMetadata() const { + return mAsyncPanZoomController->GetScrollMetadata(); +} + +bool Axis::OverscrollBehaviorAllowsHandoff() const { + // Scroll handoff is a "non-local" overscroll behavior, so it's allowed + // with "auto" and disallowed with "contain" and "none". + return GetOverscrollBehavior() == OverscrollBehavior::Auto; +} + +bool Axis::OverscrollBehaviorAllowsOverscrollEffect() const { + // An overscroll effect is a "local" overscroll behavior, so it's allowed + // with "auto" and "contain" and disallowed with "none". + return GetOverscrollBehavior() != OverscrollBehavior::None; +} + +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"; } + +bool AxisX::CanScrollTo(Side aSide) const { + switch (aSide) { + case eSideLeft: + return CanScroll(-COORDINATE_EPSILON * 2); + case eSideRight: + return CanScroll(COORDINATE_EPSILON * 2); + default: + MOZ_ASSERT_UNREACHABLE("aSide is out of valid values"); + return false; + } +} + +OverscrollBehavior AxisX::GetOverscrollBehavior() const { + return GetScrollMetadata().GetOverscrollBehavior().mBehaviorX; +} + +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"; } + +bool AxisY::CanScrollTo(Side aSide) const { + switch (aSide) { + case eSideTop: + return CanScroll(-COORDINATE_EPSILON * 2); + case eSideBottom: + return CanScroll(COORDINATE_EPSILON * 2); + default: + MOZ_ASSERT_UNREACHABLE("aSide is out of valid values"); + return false; + } +} + +bool AxisY::CanScrollDownwardsWithDynamicToolbar() const { + return GetCompositionLengthWithoutDynamicToolbar() == ParentLayerCoord(0) + ? CanScroll() + : GetPageLength() - GetCompositionLengthWithoutDynamicToolbar() > + COORDINATE_EPSILON; +} + +OverscrollBehavior AxisY::GetOverscrollBehavior() const { + return GetScrollMetadata().GetOverscrollBehavior().mBehaviorY; +} + +ParentLayerCoord AxisY::GetCompositionLengthWithoutDynamicToolbar() const { + return GetFrameMetrics().GetCompositionSizeWithoutDynamicToolbar().Height(); +} + +} // namespace layers +} // namespace mozilla |