Overscroll.h (10083B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_layers_Overscroll_h 8 #define mozilla_layers_Overscroll_h 9 10 #include "AsyncPanZoomAnimation.h" 11 #include "AsyncPanZoomController.h" 12 #include "mozilla/TimeStamp.h" 13 #include "nsThreadUtils.h" 14 15 namespace mozilla { 16 namespace layers { 17 18 // Animation used by GenericOverscrollEffect. 19 class OverscrollAnimation : public AsyncPanZoomAnimation { 20 public: 21 OverscrollAnimation(AsyncPanZoomController& aApzc, 22 const ParentLayerPoint& aVelocity, 23 SideBits aOverscrollSideBits) 24 : mApzc(aApzc), mOverscrollSideBits(aOverscrollSideBits) { 25 MOZ_ASSERT( 26 (mOverscrollSideBits & SideBits::eTopBottom) != SideBits::eTopBottom && 27 (mOverscrollSideBits & SideBits::eLeftRight) != 28 SideBits::eLeftRight, 29 "Don't allow overscrolling on both sides at the same time"); 30 if ((aOverscrollSideBits & SideBits::eLeftRight) != SideBits::eNone) { 31 mApzc.mX.StartOverscrollAnimation(aVelocity.x); 32 } 33 if ((aOverscrollSideBits & SideBits::eTopBottom) != SideBits::eNone) { 34 mApzc.mY.StartOverscrollAnimation(aVelocity.y); 35 } 36 } 37 virtual ~OverscrollAnimation() { 38 mApzc.mX.EndOverscrollAnimation(); 39 mApzc.mY.EndOverscrollAnimation(); 40 } 41 42 virtual bool DoSample(FrameMetrics& aFrameMetrics, 43 const TimeDuration& aDelta) override { 44 // Can't inline these variables due to short-circuit evaluation. 45 bool continueX = mApzc.mX.IsOverscrollAnimationAlive() && 46 mApzc.mX.SampleOverscrollAnimation( 47 aDelta, mOverscrollSideBits & SideBits::eLeftRight); 48 bool continueY = mApzc.mY.IsOverscrollAnimationAlive() && 49 mApzc.mY.SampleOverscrollAnimation( 50 aDelta, mOverscrollSideBits & SideBits::eTopBottom); 51 if (!continueX && !continueY) { 52 // If we got into overscroll from a fling, that fling did not request a 53 // fling snap to avoid a resulting scrollTo from cancelling the overscroll 54 // animation too early. We do still want to request a fling snap, though, 55 // in case the end of the axis at which we're overscrolled is not a valid 56 // snap point, so we request one now. If there are no snap points, this 57 // will do nothing. If there are snap points, we'll get a scrollTo that 58 // snaps us back to the nearest valid snap point. The scroll snapping is 59 // done in a deferred task, otherwise the state change to NOTHING caused 60 // by the overscroll animation ending would clobber a possible state 61 // change to SMOOTH_SCROLL in ScrollSnap(). 62 mDeferredTasks.AppendElement(NewRunnableMethod<ScrollSnapFlags>( 63 "layers::AsyncPanZoomController::ScrollSnap", &mApzc, 64 &AsyncPanZoomController::ScrollSnap, 65 ScrollSnapFlags::IntendedEndPosition)); 66 return false; 67 } 68 return true; 69 } 70 71 virtual bool WantsRepaints() override { return false; } 72 73 // Tell the overscroll animation about the pan momentum event. For each axis, 74 // the overscroll animation may start, stop, or continue managing that axis in 75 // response to the pan momentum event 76 void HandlePanMomentum(const ParentLayerPoint& aDisplacement) { 77 float xOverscroll = mApzc.mX.GetOverscroll(); 78 if ((xOverscroll > 0 && aDisplacement.x > 0) || 79 (xOverscroll < 0 && aDisplacement.x < 0)) { 80 if (!mApzc.mX.IsOverscrollAnimationRunning()) { 81 // Start a new overscroll animation on this axis, if there is no 82 // overscroll animation running and if the pan momentum displacement 83 // the pan momentum displacement is the same direction of the current 84 // overscroll. 85 mApzc.mX.StartOverscrollAnimation(mApzc.mX.GetVelocity()); 86 mOverscrollSideBits |= 87 xOverscroll > 0 ? SideBits::eRight : SideBits::eLeft; 88 } 89 } else if ((xOverscroll > 0 && aDisplacement.x < 0) || 90 (xOverscroll < 0 && aDisplacement.x > 0)) { 91 // Otherwise, stop the animation in the direction so that it won't clobber 92 // subsequent pan momentum scrolling. 93 mApzc.mX.EndOverscrollAnimation(); 94 } 95 96 // Same as above but for Y axis. 97 float yOverscroll = mApzc.mY.GetOverscroll(); 98 if ((yOverscroll > 0 && aDisplacement.y > 0) || 99 (yOverscroll < 0 && aDisplacement.y < 0)) { 100 if (!mApzc.mY.IsOverscrollAnimationRunning()) { 101 mApzc.mY.StartOverscrollAnimation(mApzc.mY.GetVelocity()); 102 mOverscrollSideBits |= 103 yOverscroll > 0 ? SideBits::eBottom : SideBits::eTop; 104 } 105 } else if ((yOverscroll > 0 && aDisplacement.y < 0) || 106 (yOverscroll < 0 && aDisplacement.y > 0)) { 107 mApzc.mY.EndOverscrollAnimation(); 108 } 109 } 110 111 ScrollDirections GetDirections() const { 112 ScrollDirections directions; 113 if (mApzc.mX.IsOverscrollAnimationRunning()) { 114 directions += ScrollDirection::eHorizontal; 115 } 116 if (mApzc.mY.IsOverscrollAnimationRunning()) { 117 directions += ScrollDirection::eVertical; 118 } 119 return directions; 120 }; 121 122 OverscrollAnimation* AsOverscrollAnimation() override { return this; } 123 124 bool IsManagingXAxis() const { 125 return mApzc.mX.IsOverscrollAnimationRunning(); 126 } 127 bool IsManagingYAxis() const { 128 return mApzc.mY.IsOverscrollAnimationRunning(); 129 } 130 131 private: 132 AsyncPanZoomController& mApzc; 133 SideBits mOverscrollSideBits; 134 }; 135 136 // Base class for different overscroll effects; 137 class OverscrollEffectBase { 138 public: 139 virtual ~OverscrollEffectBase() = default; 140 141 // Try to increase the amount of overscroll by |aOverscroll|. Limited to 142 // directions contained in |aOverscrollableDirections|. Components of 143 // |aOverscroll| in directions that are successfully consumed are dropped. 144 virtual void ConsumeOverscroll( 145 ParentLayerPoint& aOverscroll, 146 ScrollDirections aOverscrollableDirections) = 0; 147 148 // Relieve overscroll. Depending on the implementation, the relief may 149 // be immediate, or gradual (e.g. after an animation) but this starts 150 // the process. |aVelocity| is the current velocity of the APZC, and 151 // |aOverscrollSideBits| contains the side(s) at which the APZC is 152 // overscrolled. 153 virtual void RelieveOverscroll(const ParentLayerPoint& aVelocity, 154 SideBits aOverscrollSideBits) = 0; 155 156 virtual bool IsOverscrolled() const = 0; 157 158 // Similarly to RelieveOverscroll(), but has immediate effect 159 // (no animation). 160 virtual void ClearOverscroll() = 0; 161 }; 162 163 // A generic overscroll effect, implemented by AsyncPanZoomController itself. 164 class GenericOverscrollEffect : public OverscrollEffectBase { 165 public: 166 explicit GenericOverscrollEffect(AsyncPanZoomController& aApzc) 167 : mApzc(aApzc) {} 168 169 void ConsumeOverscroll(ParentLayerPoint& aOverscroll, 170 ScrollDirections aOverscrollableDirections) override { 171 if (aOverscrollableDirections.contains(ScrollDirection::eHorizontal)) { 172 mApzc.mX.OverscrollBy(aOverscroll.x); 173 aOverscroll.x = 0; 174 } 175 176 if (aOverscrollableDirections.contains(ScrollDirection::eVertical)) { 177 mApzc.mY.OverscrollBy(aOverscroll.y); 178 aOverscroll.y = 0; 179 } 180 181 if (!aOverscrollableDirections.isEmpty()) { 182 mApzc.ScheduleComposite(); 183 } 184 } 185 186 void RelieveOverscroll(const ParentLayerPoint& aVelocity, 187 SideBits aOverscrollSideBits) override { 188 mApzc.StartOverscrollAnimation(aVelocity, aOverscrollSideBits); 189 } 190 191 bool IsOverscrolled() const override { 192 return mApzc.IsPhysicallyOverscrolled(); 193 } 194 195 void ClearOverscroll() override { mApzc.ClearPhysicalOverscroll(); } 196 197 private: 198 AsyncPanZoomController& mApzc; 199 }; 200 201 // A widget-specific overscroll effect, implemented by the widget via 202 // GeckoContentController. 203 class WidgetOverscrollEffect : public OverscrollEffectBase { 204 public: 205 explicit WidgetOverscrollEffect(AsyncPanZoomController& aApzc) 206 : mApzc(aApzc), mIsOverscrolled(false) {} 207 208 void ConsumeOverscroll(ParentLayerPoint& aOverscroll, 209 ScrollDirections aOverscrollableDirections) override { 210 RefPtr<GeckoContentController> controller = 211 mApzc.GetGeckoContentController(); 212 if (!aOverscrollableDirections.contains(ScrollDirection::eHorizontal)) { 213 aOverscroll.x = 0; 214 } 215 216 if (!aOverscrollableDirections.contains(ScrollDirection::eVertical)) { 217 aOverscroll.y = 0; 218 } 219 220 if (controller && !aOverscrollableDirections.isEmpty()) { 221 mIsOverscrolled = true; 222 controller->UpdateOverscrollOffset(mApzc.GetGuid(), aOverscroll.x, 223 aOverscroll.y, mApzc.IsRootContent()); 224 aOverscroll = ParentLayerPoint(); 225 } 226 } 227 228 void RelieveOverscroll(const ParentLayerPoint& aVelocity, 229 SideBits aOverscrollSideBits) override { 230 if (!mIsOverscrolled) { 231 return; 232 } 233 RefPtr<GeckoContentController> controller = 234 mApzc.GetGeckoContentController(); 235 // From APZC's point of view, consider it to no longer be overscrolled 236 // as soon as RelieveOverscroll() is called. The widget may use a 237 // delay or animation until the relieving of the overscroll is complete, 238 // but we don't have any insight into that. 239 mIsOverscrolled = false; 240 if (controller) { 241 controller->UpdateOverscrollVelocity(mApzc.GetGuid(), aVelocity.x, 242 aVelocity.y, mApzc.IsRootContent()); 243 } 244 } 245 246 bool IsOverscrolled() const override { return mIsOverscrolled; } 247 248 void ClearOverscroll() override { 249 RelieveOverscroll(ParentLayerPoint(), SideBits() /* ignored */); 250 } 251 252 private: 253 AsyncPanZoomController& mApzc; 254 bool mIsOverscrolled; 255 }; 256 257 } // namespace layers 258 } // namespace mozilla 259 260 #endif // mozilla_layers_Overscroll_h