summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/src/GenericFlingAnimation.h
blob: 82f981ae0a7c1c37476246f19f21638e49f43cc7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/* -*- 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_GenericFlingAnimation_h_
#define mozilla_layers_GenericFlingAnimation_h_

#include "APZUtils.h"
#include "AsyncPanZoomAnimation.h"
#include "AsyncPanZoomController.h"
#include "FrameMetrics.h"
#include "Units.h"
#include "OverscrollHandoffState.h"
#include "mozilla/Assertions.h"
#include "mozilla/Monitor.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/ToString.h"
#include "nsThreadUtils.h"

static mozilla::LazyLogModule sApzFlgLog("apz.fling");
#define FLING_LOG(...) MOZ_LOG(sApzFlgLog, LogLevel::Debug, (__VA_ARGS__))

namespace mozilla {
namespace layers {

/**
 * The FlingPhysics template parameter determines the physics model
 * that the fling animation follows. It must have the following methods:
 *
 *   - Default constructor.
 *
 *   - Init(const ParentLayerPoint& aStartingVelocity, float aPLPPI).
 *     Called at the beginning of the fling, with the fling's starting velocity,
 *     and the number of ParentLayer pixels per (Screen) inch at the point of
 *     the fling's start in the fling's direction.
 *
 *   - Sample(const TimeDuration& aDelta,
 *            ParentLayerPoint* aOutVelocity,
 *            ParentLayerPoint* aOutOffset);
 *     Called on each sample of the fling.
 *     |aDelta| is the time elapsed since the last sample.
 *     |aOutVelocity| should be the desired velocity after the current sample,
 *                    in ParentLayer pixels per millisecond.
 *     |aOutOffset| should be the desired _delta_ to the scroll offset after
 *     the current sample. |aOutOffset| should _not_ be clamped to the APZC's
 *     scrollable bounds; the caller will do the clamping, and it needs to
 *     know the unclamped value to handle handoff/overscroll correctly.
 */
template <typename FlingPhysics>
class GenericFlingAnimation : public AsyncPanZoomAnimation,
                              public FlingPhysics {
 public:
  GenericFlingAnimation(AsyncPanZoomController& aApzc,
                        const FlingHandoffState& aHandoffState, float aPLPPI)
      : mApzc(aApzc),
        mOverscrollHandoffChain(aHandoffState.mChain),
        mScrolledApzc(aHandoffState.mScrolledApzc) {
    MOZ_ASSERT(mOverscrollHandoffChain);

    // Drop any velocity on axes where we don't have room to scroll anyways
    // (in this APZC, or an APZC further in the handoff chain).
    // This ensures that we don't take the 'overscroll' path in Sample()
    // on account of one axis which can't scroll having a velocity.
    if (!mOverscrollHandoffChain->CanScrollInDirection(
            &mApzc, ScrollDirection::eHorizontal)) {
      RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex);
      mApzc.mX.SetVelocity(0);
    }
    if (!mOverscrollHandoffChain->CanScrollInDirection(
            &mApzc, ScrollDirection::eVertical)) {
      RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex);
      mApzc.mY.SetVelocity(0);
    }

    if (aHandoffState.mIsHandoff) {
      // Only apply acceleration in the APZC that originated the fling, not in
      // APZCs further down the handoff chain during handoff.
      mApzc.mFlingAccelerator.Reset();
    }

    ParentLayerPoint velocity =
        mApzc.mFlingAccelerator.GetFlingStartingVelocity(
            aApzc.GetFrameTime(), mApzc.GetVelocityVector(), aHandoffState);

    mApzc.SetVelocityVector(velocity);

    FlingPhysics::Init(mApzc.GetVelocityVector(), aPLPPI);
  }

  /**
   * Advances a fling by an interpolated amount based on the passed in |aDelta|.
   * This should be called whenever sampling the content transform for this
   * frame. Returns true if the fling animation should be advanced by one frame,
   * or false if there is no fling or the fling has ended.
   */
  virtual bool DoSample(FrameMetrics& aFrameMetrics,
                        const TimeDuration& aDelta) override {
    CSSToParentLayerScale zoom(aFrameMetrics.GetZoom());
    if (zoom == CSSToParentLayerScale(0)) {
      return false;
    }

    ParentLayerPoint velocity;
    ParentLayerPoint offset;
    FlingPhysics::Sample(aDelta, &velocity, &offset);

    mApzc.SetVelocityVector(velocity);

    // If we shouldn't continue the fling, let's just stop and repaint.
    if (IsZero(velocity / zoom)) {
      FLING_LOG("%p ending fling animation. overscrolled=%d\n", &mApzc,
                mApzc.IsOverscrolled());
      // This APZC or an APZC further down the handoff chain may be be
      // overscrolled. Start a snap-back animation on the overscrolled APZC.
      // Note:
      //   This needs to be a deferred task even though it can safely run
      //   while holding mRecursiveMutex, because otherwise, if the overscrolled
      //   APZC is this one, then the SetState(NOTHING) in UpdateAnimation will
      //   stomp on the SetState(SNAP_BACK) it does.
      mDeferredTasks.AppendElement(NewRunnableMethod<AsyncPanZoomController*>(
          "layers::OverscrollHandoffChain::SnapBackOverscrolledApzc",
          mOverscrollHandoffChain.get(),
          &OverscrollHandoffChain::SnapBackOverscrolledApzc, &mApzc));
      return false;
    }

    // Ordinarily we might need to do a ScheduleComposite if either of
    // the following AdjustDisplacement calls returns true, but this
    // is already running as part of a FlingAnimation, so we'll be compositing
    // per frame of animation anyway.
    ParentLayerPoint overscroll;
    ParentLayerPoint adjustedOffset;
    mApzc.mX.AdjustDisplacement(offset.x, adjustedOffset.x, overscroll.x);
    mApzc.mY.AdjustDisplacement(offset.y, adjustedOffset.y, overscroll.y);
    if (aFrameMetrics.GetZoom() != CSSToParentLayerScale(0)) {
      mApzc.ScrollBy(adjustedOffset / aFrameMetrics.GetZoom());
    }

    // The fling may have caused us to reach the end of our scroll range.
    if (!IsZero(overscroll / zoom)) {
      // Hand off the fling to the next APZC in the overscroll handoff chain.

      // We may have reached the end of the scroll range along one axis but
      // not the other. In such a case we only want to hand off the relevant
      // component of the fling.
      if (mApzc.IsZero(overscroll.x)) {
        velocity.x = 0;
      } else if (mApzc.IsZero(overscroll.y)) {
        velocity.y = 0;
      }

      // To hand off the fling, we attempt to find a target APZC and start a new
      // fling with the same velocity on that APZC. For simplicity, the actual
      // overscroll of the current sample is discarded rather than being handed
      // off. The compositor should sample animations sufficiently frequently
      // that this is not noticeable. The target APZC is chosen by seeing if
      // there is an APZC further in the handoff chain which is pannable; if
      // there isn't, we take the new fling ourselves, entering an overscrolled
      // state.
      // Note: APZC is holding mRecursiveMutex, so directly calling
      // HandleFlingOverscroll() (which acquires the tree lock) would violate
      // the lock ordering. Instead we schedule HandleFlingOverscroll() to be
      // called after mRecursiveMutex is released.
      FLING_LOG("%p fling went into overscroll, handing off with velocity %s\n",
                &mApzc, ToString(velocity).c_str());
      mDeferredTasks.AppendElement(
          NewRunnableMethod<ParentLayerPoint, SideBits,
                            RefPtr<const OverscrollHandoffChain>,
                            RefPtr<const AsyncPanZoomController>>(
              "layers::AsyncPanZoomController::HandleFlingOverscroll", &mApzc,
              &AsyncPanZoomController::HandleFlingOverscroll, velocity,
              apz::GetOverscrollSideBits(overscroll), mOverscrollHandoffChain,
              mScrolledApzc));

      // If there is a remaining velocity on this APZC, continue this fling
      // as well. (This fling and the handed-off fling will run concurrently.)
      // Note that AdjustDisplacement() will have zeroed out the velocity
      // along the axes where we're overscrolled.
      return !IsZero(mApzc.GetVelocityVector() / zoom);
    }

    return true;
  }

  void Cancel(CancelAnimationFlags aFlags) override {
    mApzc.mFlingAccelerator.ObserveFlingCanceled(mApzc.GetVelocityVector());
  }

  virtual bool HandleScrollOffsetUpdate(
      const Maybe<CSSPoint>& aRelativeDelta) override {
    return true;
  }

 private:
  AsyncPanZoomController& mApzc;
  RefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
  RefPtr<const AsyncPanZoomController> mScrolledApzc;
};

}  // namespace layers
}  // namespace mozilla

#endif  // mozilla_layers_GenericFlingAnimation_h_