tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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