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_Axis_h
8 #define mozilla_layers_Axis_h
9 
10 #include <sys/types.h>  // for int32_t
11 
12 #include "APZUtils.h"
13 #include "AxisPhysicsMSDModel.h"
14 #include "mozilla/DataMutex.h"  // for DataMutex
15 #include "mozilla/gfx/Types.h"  // for Side
16 #include "mozilla/TimeStamp.h"  // for TimeDuration
17 #include "nsTArray.h"           // for nsTArray
18 #include "Units.h"
19 
20 namespace mozilla {
21 namespace layers {
22 
23 const float EPSILON = 0.0001f;
24 
25 /**
26  * Compare two coordinates for equality, accounting for rounding error.
27  * Use both FuzzyEqualsAdditive() with COORDINATE_EPISLON, which accounts for
28  * things like the error introduced by rounding during a round-trip to app
29  * units, and FuzzyEqualsMultiplicative(), which accounts for accumulated error
30  * due to floating-point operations (which can be larger than COORDINATE_EPISLON
31  * for sufficiently large coordinate values).
32  */
33 bool FuzzyEqualsCoordinate(float aValue1, float aValue2);
34 
35 struct FrameMetrics;
36 class AsyncPanZoomController;
37 
38 /**
39  * Interface for computing velocities along the axis based on
40  * position samples.
41  */
42 class VelocityTracker {
43  public:
44   virtual ~VelocityTracker() = default;
45 
46   /**
47    * Start tracking velocity along this axis, starting with the given
48    * initial position and corresponding timestamp.
49    */
50   virtual void StartTracking(ParentLayerCoord aPos, TimeStamp aTimestamp) = 0;
51   /**
52    * Record a new position along this axis, at the given timestamp.
53    * Returns the average velocity between the last sample and this one, or
54    * or Nothing() if a reasonable average cannot be computed.
55    */
56   virtual Maybe<float> AddPosition(ParentLayerCoord aPos,
57                                    TimeStamp aTimestamp) = 0;
58   /**
59    * Compute an estimate of the axis's current velocity, based on recent
60    * position samples. It's up to implementation how many samples to consider
61    * and how to perform the computation.
62    * If the tracker doesn't have enough samples to compute a result, it
63    * may return Nothing{}.
64    */
65   virtual Maybe<float> ComputeVelocity(TimeStamp aTimestamp) = 0;
66   /**
67    * Clear all state in the velocity tracker.
68    */
69   virtual void Clear() = 0;
70 };
71 
72 /**
73  * Helper class to maintain each axis of movement (X,Y) for panning and zooming.
74  * Note that everything here is specific to one axis; that is, the X axis knows
75  * nothing about the Y axis and vice versa.
76  */
77 class Axis {
78  public:
79   explicit Axis(AsyncPanZoomController* aAsyncPanZoomController);
80 
81   /**
82    * Notify this Axis that a new touch has been received, including a timestamp
83    * for when the touch was received. This triggers a recalculation of velocity.
84    * This can also used for pan gesture events. For those events, |aPos| is
85    * an invented position corresponding to the mouse position plus any
86    * accumulated displacements over the course of the pan gesture.
87    */
88   void UpdateWithTouchAtDevicePoint(ParentLayerCoord aPos,
89                                     TimeStamp aTimestamp);
90 
91  public:
92   /**
93    * Notify this Axis that a touch has begun, i.e. the user has put their finger
94    * on the screen but has not yet tried to pan.
95    */
96   void StartTouch(ParentLayerCoord aPos, TimeStamp aTimestamp);
97 
98   /**
99    * Notify this Axis that a touch has ended gracefully. This may perform
100    * recalculations of the axis velocity.
101    */
102   void EndTouch(TimeStamp aTimestamp);
103 
104   /**
105    * Notify this Axis that the gesture has ended forcefully. Useful for stopping
106    * flings when a user puts their finger down in the middle of one (i.e. to
107    * stop a previous touch including its fling so that a new one can take its
108    * place).
109    */
110   void CancelGesture();
111 
112   /**
113    * Takes a requested displacement to the position of this axis, and adjusts it
114    * to account for overscroll (which might decrease the displacement; this is
115    * to prevent the viewport from overscrolling the page rect), and axis locking
116    * (which might prevent any displacement from happening). If overscroll
117    * ocurred, its amount is written to |aOverscrollAmountOut|.
118    * The |aDisplacementOut| parameter is set to the adjusted displacement, and
119    * the function returns true if and only if internal overscroll amounts were
120    * changed.
121    */
122   bool AdjustDisplacement(ParentLayerCoord aDisplacement,
123                           /* ParentLayerCoord */ float& aDisplacementOut,
124                           /* ParentLayerCoord */ float& aOverscrollAmountOut,
125                           bool aForceOverscroll = false);
126 
127   /**
128    * Overscrolls this axis by the requested amount in the requested direction.
129    * The axis must be at the end of its scroll range in this direction.
130    */
131   void OverscrollBy(ParentLayerCoord aOverscroll);
132 
133   /**
134    * Return the amount of overscroll on this axis, in ParentLayer pixels.
135    *
136    * If this amount is nonzero, the relevant component of
137    * mAsyncPanZoomController->Metrics().mScrollOffset must be at its
138    * extreme allowed value in the relevant direction (that is, it must be at
139    * its maximum value if we are overscrolled at our composition length, and
140    * at its minimum value if we are overscrolled at the origin).
141    */
142   ParentLayerCoord GetOverscroll() const;
143 
144   /**
145    * Restore the amount by which this axis is overscrolled to the specified
146    * amount. This is for test-related use; overscrolling as a result of user
147    * input should happen via OverscrollBy().
148    */
149   void RestoreOverscroll(ParentLayerCoord aOverscroll);
150 
151   /**
152    * Start an overscroll animation with the given initial velocity.
153    */
154   void StartOverscrollAnimation(float aVelocity);
155 
156   /**
157    * Sample the snap-back animation to relieve overscroll.
158    * |aDelta| is the time since the last sample.
159    */
160   bool SampleOverscrollAnimation(const TimeDuration& aDelta);
161 
162   /**
163    * Stop an overscroll animation.
164    */
165   void EndOverscrollAnimation();
166 
167   /**
168    * Return whether this axis is overscrolled in either direction.
169    */
170   bool IsOverscrolled() const;
171 
172   /**
173    * Return true if this axis is overscrolled but its scroll offset
174    * has changed in a way that makes the oversrolled state no longer
175    * valid (for example, it is overscrolled at the top but the
176    * scroll offset is no longer zero).
177    */
178   bool IsInInvalidOverscroll() const;
179 
180   /**
181    * Clear any overscroll amount on this axis.
182    */
183   void ClearOverscroll();
184 
185   /**
186    * Returns whether the overscroll animation is alive.
187    */
188   bool IsOverscrollAnimationAlive() const;
189 
190   /**
191    * Returns whether the overscroll animation is running.
192    * Note that unlike the above IsOverscrollAnimationAlive, this function
193    * returns false even if the animation is still there but is very close to
194    * the destination position and its velocity is quite low, i.e. it's time to
195    * finish.
196    */
197   bool IsOverscrollAnimationRunning() const;
198 
199   /**
200    * Gets the starting position of the touch supplied in StartTouch().
201    */
202   ParentLayerCoord PanStart() const;
203 
204   /**
205    * Gets the distance between the starting position of the touch supplied in
206    * StartTouch() and the current touch from the last
207    * UpdateWithTouchAtDevicePoint().
208    */
209   ParentLayerCoord PanDistance() const;
210 
211   /**
212    * Gets the distance between the starting position of the touch supplied in
213    * StartTouch() and the supplied position.
214    */
215   ParentLayerCoord PanDistance(ParentLayerCoord aPos) const;
216 
217   /**
218    * Returns true if the page has room to be scrolled along this axis.
219    */
220   bool CanScroll() const;
221 
222   /**
223    * Returns whether this axis can scroll any more in a particular direction.
224    */
225   bool CanScroll(ParentLayerCoord aDelta) const;
226 
227   /**
228    * Returns true if the page has room to be scrolled along this axis
229    * and this axis is not scroll-locked.
230    */
231   bool CanScrollNow() const;
232 
233   /**
234    * Clamp a point to the page's scrollable bounds. That is, a scroll
235    * destination to the returned point will not contain any overscroll.
236    */
237   CSSCoord ClampOriginToScrollableRect(CSSCoord aOrigin) const;
238 
SetAxisLocked(bool aAxisLocked)239   void SetAxisLocked(bool aAxisLocked) { mAxisLocked = aAxisLocked; }
240 
241   /**
242    * Gets the raw velocity of this axis at this moment.
243    */
244   float GetVelocity() const;
245 
246   /**
247    * Sets the raw velocity of this axis at this moment.
248    * Intended to be called only when the axis "takes over" a velocity from
249    * another APZC, in which case there are no touch points available to call
250    * UpdateWithTouchAtDevicePoint. In other circumstances,
251    * UpdateWithTouchAtDevicePoint should be used and the velocity calculated
252    * there.
253    */
254   void SetVelocity(float aVelocity);
255 
256   /**
257    * If a displacement will overscroll the axis, this returns the amount and in
258    * what direction.
259    */
260   ParentLayerCoord DisplacementWillOverscrollAmount(
261       ParentLayerCoord aDisplacement) const;
262 
263   /**
264    * If a scale will overscroll the axis, this returns the amount and in what
265    * direction.
266    *
267    * |aFocus| is the point at which the scale is focused at. We will offset the
268    * scroll offset in such a way that it remains in the same place on the page
269    * relative.
270    *
271    * Note: Unlike most other functions in Axis, this functions operates in
272    *       CSS coordinates so there is no confusion as to whether the
273    *       ParentLayer coordinates it operates in are before or after the scale
274    *       is applied.
275    */
276   CSSCoord ScaleWillOverscrollAmount(float aScale, CSSCoord aFocus) const;
277 
278   /**
279    * Checks if an axis will overscroll in both directions by computing the
280    * content rect and checking that its height/width (depending on the axis)
281    * does not overextend past the viewport.
282    *
283    * This gets called by ScaleWillOverscroll().
284    */
285   bool ScaleWillOverscrollBothSides(float aScale) const;
286 
287   /**
288    * Returns true if movement on this axis is locked.
289    */
290   bool IsAxisLocked() const;
291 
292   ParentLayerCoord GetOrigin() const;
293   ParentLayerCoord GetCompositionLength() const;
294   ParentLayerCoord GetPageStart() const;
295   ParentLayerCoord GetPageLength() const;
296   ParentLayerCoord GetCompositionEnd() const;
297   ParentLayerCoord GetPageEnd() const;
298   ParentLayerCoord GetScrollRangeEnd() const;
299 
300   bool IsScrolledToStart() const;
301   bool IsScrolledToEnd() const;
302 
GetPos()303   ParentLayerCoord GetPos() const { return mPos; }
304 
305   bool OverscrollBehaviorAllowsHandoff() const;
306   bool OverscrollBehaviorAllowsOverscrollEffect() const;
307 
308   virtual CSSToParentLayerScale GetAxisScale(
309       const CSSToParentLayerScale2D& aScale) const = 0;
310   virtual CSSCoord GetPointOffset(const CSSPoint& aPoint) const = 0;
311   virtual ParentLayerCoord GetPointOffset(
312       const ParentLayerPoint& aPoint) const = 0;
313   virtual ParentLayerCoord GetRectLength(
314       const ParentLayerRect& aRect) const = 0;
315   virtual ParentLayerCoord GetRectOffset(
316       const ParentLayerRect& aRect) const = 0;
317   virtual float GetTransformScale(
318       const AsyncTransformComponentMatrix& aMatrix) const = 0;
319   virtual ParentLayerCoord GetTransformTranslation(
320       const AsyncTransformComponentMatrix& aMatrix) const = 0;
321   virtual void PostScale(AsyncTransformComponentMatrix& aMatrix,
322                          float aScale) const = 0;
323   virtual void PostTranslate(AsyncTransformComponentMatrix& aMatrix,
324                              ParentLayerCoord aTranslation) const = 0;
325 
326   virtual ScreenPoint MakePoint(ScreenCoord aCoord) const = 0;
327 
OpaqueApzcPointer()328   const void* OpaqueApzcPointer() const { return mAsyncPanZoomController; }
329 
330   virtual const char* Name() const = 0;
331 
332   // Convert a velocity from global inches/ms into ParentLayerCoords/ms.
333   float ToLocalVelocity(float aVelocityInchesPerMs) const;
334 
335  protected:
336   // A position along the axis, used during input event processing to
337   // track velocities (and for touch gestures, to track the length of
338   // the gesture). For touch events, this represents the position of
339   // the finger (or in the case of two-finger scrolling, the midpoint
340   // of the two fingers). For pan gesture events, this represents an
341   // invented position corresponding to the mouse position at the start
342   // of the pan, plus deltas representing the displacement of the pan.
343   ParentLayerCoord mPos;
344 
345   ParentLayerCoord mStartPos;
346   // The velocity can be accessed from multiple threads (e.g. APZ
347   // controller thread and APZ sampler thread), so needs to be
348   // protected by a mutex.
349   // Units: ParentLayerCoords per millisecond
350   mutable DataMutex<float> mVelocity;
351   bool mAxisLocked;  // Whether movement on this axis is locked.
352   AsyncPanZoomController* mAsyncPanZoomController;
353 
354   // The amount by which we are overscrolled; see GetOverscroll().
355   ParentLayerCoord mOverscroll;
356 
357   // The mass-spring-damper model for overscroll physics.
358   AxisPhysicsMSDModel mMSDModel;
359 
360   // Used to track velocity over a series of input events and compute
361   // a resulting velocity to use for e.g. starting a fling animation.
362   // This member can only be accessed on the controller/UI thread.
363   UniquePtr<VelocityTracker> mVelocityTracker;
364 
365   float DoGetVelocity() const;
366   void DoSetVelocity(float aVelocity);
367 
368   const FrameMetrics& GetFrameMetrics() const;
369   const ScrollMetadata& GetScrollMetadata() const;
370 
371   virtual OverscrollBehavior GetOverscrollBehavior() const = 0;
372 
373   // Adjust a requested overscroll amount for resistance, yielding a smaller
374   // actual overscroll amount.
375   ParentLayerCoord ApplyResistance(ParentLayerCoord aOverscroll) const;
376 
377   // Helper function for SampleOverscrollAnimation().
378   void StepOverscrollAnimation(double aStepDurationMilliseconds);
379 };
380 
381 class AxisX : public Axis {
382  public:
383   explicit AxisX(AsyncPanZoomController* mAsyncPanZoomController);
384   CSSToParentLayerScale GetAxisScale(
385       const CSSToParentLayerScale2D& aScale) const override;
386   CSSCoord GetPointOffset(const CSSPoint& aPoint) const override;
387   ParentLayerCoord GetPointOffset(
388       const ParentLayerPoint& aPoint) const override;
389   ParentLayerCoord GetRectLength(const ParentLayerRect& aRect) const override;
390   ParentLayerCoord GetRectOffset(const ParentLayerRect& aRect) const override;
391   float GetTransformScale(
392       const AsyncTransformComponentMatrix& aMatrix) const override;
393   ParentLayerCoord GetTransformTranslation(
394       const AsyncTransformComponentMatrix& aMatrix) const override;
395   void PostScale(AsyncTransformComponentMatrix& aMatrix,
396                  float aScale) const override;
397   void PostTranslate(AsyncTransformComponentMatrix& aMatrix,
398                      ParentLayerCoord aTranslation) const override;
399   ScreenPoint MakePoint(ScreenCoord aCoord) const override;
400   const char* Name() const override;
401   bool CanScrollTo(Side aSide) const;
402   SideBits ScrollableDirections() const;
403 
404  private:
405   OverscrollBehavior GetOverscrollBehavior() const override;
406 };
407 
408 class AxisY : public Axis {
409  public:
410   explicit AxisY(AsyncPanZoomController* mAsyncPanZoomController);
411   CSSCoord GetPointOffset(const CSSPoint& aPoint) const override;
412   ParentLayerCoord GetPointOffset(
413       const ParentLayerPoint& aPoint) const override;
414   CSSToParentLayerScale GetAxisScale(
415       const CSSToParentLayerScale2D& aScale) const override;
416   ParentLayerCoord GetRectLength(const ParentLayerRect& aRect) const override;
417   ParentLayerCoord GetRectOffset(const ParentLayerRect& aRect) const override;
418   float GetTransformScale(
419       const AsyncTransformComponentMatrix& aMatrix) const override;
420   ParentLayerCoord GetTransformTranslation(
421       const AsyncTransformComponentMatrix& aMatrix) const override;
422   void PostScale(AsyncTransformComponentMatrix& aMatrix,
423                  float aScale) const override;
424   void PostTranslate(AsyncTransformComponentMatrix& aMatrix,
425                      ParentLayerCoord aTranslation) const override;
426   ScreenPoint MakePoint(ScreenCoord aCoord) const override;
427   const char* Name() const override;
428   bool CanScrollTo(Side aSide) const;
429   bool CanVerticalScrollWithDynamicToolbar() const;
430   SideBits ScrollableDirections() const;
431   SideBits ScrollableDirectionsWithDynamicToolbar(
432       const ScreenMargin& aFixedLayerMargins) const;
433 
434  private:
435   OverscrollBehavior GetOverscrollBehavior() const override;
436   ParentLayerCoord GetCompositionLengthWithoutDynamicToolbar() const;
437   bool HasDynamicToolbar() const;
438 };
439 
440 }  // namespace layers
441 }  // namespace mozilla
442 
443 #endif
444