diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/layers/apz/src/Overscroll.h | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/gfx/layers/apz/src/Overscroll.h b/gfx/layers/apz/src/Overscroll.h new file mode 100644 index 0000000000..1fb7c3e487 --- /dev/null +++ b/gfx/layers/apz/src/Overscroll.h @@ -0,0 +1,250 @@ +/* -*- 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_Overscroll_h +#define mozilla_layers_Overscroll_h + +#include "AsyncPanZoomAnimation.h" +#include "AsyncPanZoomController.h" +#include "mozilla/TimeStamp.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace layers { + +// Animation used by GenericOverscrollEffect. +class OverscrollAnimation : public AsyncPanZoomAnimation { + public: + OverscrollAnimation(AsyncPanZoomController& aApzc, + const ParentLayerPoint& aVelocity, + SideBits aOverscrollSideBits) + : mApzc(aApzc), mOverscrollSideBits(aOverscrollSideBits) { + MOZ_ASSERT( + (mOverscrollSideBits & SideBits::eTopBottom) != SideBits::eTopBottom && + (mOverscrollSideBits & SideBits::eLeftRight) != + SideBits::eLeftRight, + "Don't allow overscrolling on both sides at the same time"); + if ((aOverscrollSideBits & SideBits::eLeftRight) != SideBits::eNone) { + mApzc.mX.StartOverscrollAnimation(aVelocity.x); + } + if ((aOverscrollSideBits & SideBits::eTopBottom) != SideBits::eNone) { + mApzc.mY.StartOverscrollAnimation(aVelocity.y); + } + } + virtual ~OverscrollAnimation() { + mApzc.mX.EndOverscrollAnimation(); + mApzc.mY.EndOverscrollAnimation(); + } + + virtual bool DoSample(FrameMetrics& aFrameMetrics, + const TimeDuration& aDelta) override { + // Can't inline these variables due to short-circuit evaluation. + bool continueX = mApzc.mX.IsOverscrollAnimationAlive() && + mApzc.mX.SampleOverscrollAnimation( + aDelta, mOverscrollSideBits & SideBits::eLeftRight); + bool continueY = mApzc.mY.IsOverscrollAnimationAlive() && + mApzc.mY.SampleOverscrollAnimation( + aDelta, mOverscrollSideBits & SideBits::eTopBottom); + if (!continueX && !continueY) { + // If we got into overscroll from a fling, that fling did not request a + // fling snap to avoid a resulting scrollTo from cancelling the overscroll + // animation too early. We do still want to request a fling snap, though, + // in case the end of the axis at which we're overscrolled is not a valid + // snap point, so we request one now. If there are no snap points, this + // will do nothing. If there are snap points, we'll get a scrollTo that + // snaps us back to the nearest valid snap point. The scroll snapping is + // done in a deferred task, otherwise the state change to NOTHING caused + // by the overscroll animation ending would clobber a possible state + // change to SMOOTH_SCROLL in ScrollSnap(). + mDeferredTasks.AppendElement(NewRunnableMethod<ScrollSnapFlags>( + "layers::AsyncPanZoomController::ScrollSnap", &mApzc, + &AsyncPanZoomController::ScrollSnap, + ScrollSnapFlags::IntendedDirection | + ScrollSnapFlags::IntendedEndPosition)); + return false; + } + return true; + } + + virtual bool WantsRepaints() override { return false; } + + // Tell the overscroll animation about the pan momentum event. For each axis, + // the overscroll animation may start, stop, or continue managing that axis in + // response to the pan momentum event + void HandlePanMomentum(const ParentLayerPoint& aDisplacement) { + float xOverscroll = mApzc.mX.GetOverscroll(); + if ((xOverscroll > 0 && aDisplacement.x > 0) || + (xOverscroll < 0 && aDisplacement.x < 0)) { + if (!mApzc.mX.IsOverscrollAnimationRunning()) { + // Start a new overscroll animation on this axis, if there is no + // overscroll animation running and if the pan momentum displacement + // the pan momentum displacement is the same direction of the current + // overscroll. + mApzc.mX.StartOverscrollAnimation(mApzc.mX.GetVelocity()); + mOverscrollSideBits |= + xOverscroll > 0 ? SideBits::eRight : SideBits::eLeft; + } + } else if ((xOverscroll > 0 && aDisplacement.x < 0) || + (xOverscroll < 0 && aDisplacement.x > 0)) { + // Otherwise, stop the animation in the direction so that it won't clobber + // subsequent pan momentum scrolling. + mApzc.mX.EndOverscrollAnimation(); + } + + // Same as above but for Y axis. + float yOverscroll = mApzc.mY.GetOverscroll(); + if ((yOverscroll > 0 && aDisplacement.y > 0) || + (yOverscroll < 0 && aDisplacement.y < 0)) { + if (!mApzc.mY.IsOverscrollAnimationRunning()) { + mApzc.mY.StartOverscrollAnimation(mApzc.mY.GetVelocity()); + mOverscrollSideBits |= + yOverscroll > 0 ? SideBits::eBottom : SideBits::eTop; + } + } else if ((yOverscroll > 0 && aDisplacement.y < 0) || + (yOverscroll < 0 && aDisplacement.y > 0)) { + mApzc.mY.EndOverscrollAnimation(); + } + } + + ScrollDirections GetDirections() const { + ScrollDirections directions; + if (mApzc.mX.IsOverscrollAnimationRunning()) { + directions += ScrollDirection::eHorizontal; + } + if (mApzc.mY.IsOverscrollAnimationRunning()) { + directions += ScrollDirection::eVertical; + } + return directions; + }; + + OverscrollAnimation* AsOverscrollAnimation() override { return this; } + + bool IsManagingXAxis() const { + return mApzc.mX.IsOverscrollAnimationRunning(); + } + bool IsManagingYAxis() const { + return mApzc.mY.IsOverscrollAnimationRunning(); + } + + private: + AsyncPanZoomController& mApzc; + SideBits mOverscrollSideBits; +}; + +// Base class for different overscroll effects; +class OverscrollEffectBase { + public: + virtual ~OverscrollEffectBase() = default; + + // Try to increase the amount of overscroll by |aOverscroll|. Limited to + // directions contained in |aOverscrollableDirections|. Components of + // |aOverscroll| in directions that are successfully consumed are dropped. + virtual void ConsumeOverscroll( + ParentLayerPoint& aOverscroll, + ScrollDirections aOverscrollableDirections) = 0; + + // Relieve overscroll. Depending on the implementation, the relief may + // be immediate, or gradual (e.g. after an animation) but this starts + // the process. |aVelocity| is the current velocity of the APZC, and + // |aOverscrollSideBits| contains the side(s) at which the APZC is + // overscrolled. + virtual void RelieveOverscroll(const ParentLayerPoint& aVelocity, + SideBits aOverscrollSideBits) = 0; + + virtual bool IsOverscrolled() const = 0; + + // Similarly to RelieveOverscroll(), but has immediate effect + // (no animation). + virtual void ClearOverscroll() = 0; +}; + +// A generic overscroll effect, implemented by AsyncPanZoomController itself. +class GenericOverscrollEffect : public OverscrollEffectBase { + public: + explicit GenericOverscrollEffect(AsyncPanZoomController& aApzc) + : mApzc(aApzc) {} + + void ConsumeOverscroll(ParentLayerPoint& aOverscroll, + ScrollDirections aOverscrollableDirections) override { + if (aOverscrollableDirections.contains(ScrollDirection::eHorizontal)) { + mApzc.mX.OverscrollBy(aOverscroll.x); + aOverscroll.x = 0; + } + + if (aOverscrollableDirections.contains(ScrollDirection::eVertical)) { + mApzc.mY.OverscrollBy(aOverscroll.y); + aOverscroll.y = 0; + } + + if (!aOverscrollableDirections.isEmpty()) { + mApzc.ScheduleComposite(); + } + } + + void RelieveOverscroll(const ParentLayerPoint& aVelocity, + SideBits aOverscrollSideBits) override { + mApzc.StartOverscrollAnimation(aVelocity, aOverscrollSideBits); + } + + bool IsOverscrolled() const override { + return mApzc.IsPhysicallyOverscrolled(); + } + + void ClearOverscroll() override { mApzc.ClearPhysicalOverscroll(); } + + private: + AsyncPanZoomController& mApzc; +}; + +// A widget-specific overscroll effect, implemented by the widget via +// GeckoContentController. +class WidgetOverscrollEffect : public OverscrollEffectBase { + public: + explicit WidgetOverscrollEffect(AsyncPanZoomController& aApzc) + : mApzc(aApzc), mIsOverscrolled(false) {} + + void ConsumeOverscroll(ParentLayerPoint& aOverscroll, + ScrollDirections aOverscrollableDirections) override { + RefPtr<GeckoContentController> controller = + mApzc.GetGeckoContentController(); + if (controller && !aOverscrollableDirections.isEmpty()) { + mIsOverscrolled = true; + controller->UpdateOverscrollOffset(mApzc.GetGuid(), aOverscroll.x, + aOverscroll.y, mApzc.IsRootContent()); + aOverscroll = ParentLayerPoint(); + } + } + + void RelieveOverscroll(const ParentLayerPoint& aVelocity, + SideBits aOverscrollSideBits) override { + RefPtr<GeckoContentController> controller = + mApzc.GetGeckoContentController(); + // From APZC's point of view, consider it to no longer be overscrolled + // as soon as RelieveOverscroll() is called. The widget may use a + // delay or animation until the relieving of the overscroll is complete, + // but we don't have any insight into that. + mIsOverscrolled = false; + if (controller) { + controller->UpdateOverscrollVelocity(mApzc.GetGuid(), aVelocity.x, + aVelocity.y, mApzc.IsRootContent()); + } + } + + bool IsOverscrolled() const override { return mIsOverscrolled; } + + void ClearOverscroll() override { + RelieveOverscroll(ParentLayerPoint(), SideBits() /* ignored */); + } + + private: + AsyncPanZoomController& mApzc; + bool mIsOverscrolled; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_Overscroll_h |