Bug 1253840 - Remove .ini file as it's no longer necessary by passing on all platform...
[gecko.git] / widget / SwipeTracker.cpp
blob284e476b08d3d9e7684283eeb4cc88fd7ff75dd5
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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "SwipeTracker.h"
9 #include "InputData.h"
10 #include "mozilla/FlushType.h"
11 #include "mozilla/PresShell.h"
12 #include "mozilla/StaticPrefs_widget.h"
13 #include "mozilla/StaticPrefs_browser.h"
14 #include "mozilla/TimeStamp.h"
15 #include "mozilla/TouchEvents.h"
16 #include "mozilla/dom/SimpleGestureEventBinding.h"
17 #include "nsAlgorithm.h"
18 #include "nsIWidget.h"
19 #include "nsRefreshDriver.h"
20 #include "UnitTransforms.h"
22 // These values were tweaked to make the physics feel similar to the native
23 // swipe.
24 static const double kSpringForce = 250.0;
25 static const double kSwipeSuccessThreshold = 0.25;
27 namespace mozilla {
29 static already_AddRefed<nsRefreshDriver> GetRefreshDriver(nsIWidget& aWidget) {
30 nsIWidgetListener* widgetListener = aWidget.GetWidgetListener();
31 PresShell* presShell =
32 widgetListener ? widgetListener->GetPresShell() : nullptr;
33 nsPresContext* presContext =
34 presShell ? presShell->GetPresContext() : nullptr;
35 RefPtr<nsRefreshDriver> refreshDriver =
36 presContext ? presContext->RefreshDriver() : nullptr;
37 return refreshDriver.forget();
40 SwipeTracker::SwipeTracker(nsIWidget& aWidget,
41 const PanGestureInput& aSwipeStartEvent,
42 uint32_t aAllowedDirections,
43 uint32_t aSwipeDirection)
44 : mWidget(aWidget),
45 mRefreshDriver(GetRefreshDriver(mWidget)),
46 mAxis(0.0, 0.0, 0.0, kSpringForce, 1.0),
47 mEventPosition(RoundedToInt(ViewAs<LayoutDevicePixel>(
48 aSwipeStartEvent.mPanStartPoint,
49 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent))),
50 mLastEventTimeStamp(aSwipeStartEvent.mTimeStamp),
51 mAllowedDirections(aAllowedDirections),
52 mSwipeDirection(aSwipeDirection),
53 mGestureAmount(0.0),
54 mCurrentVelocity(0.0),
55 mEventsAreControllingSwipe(true),
56 mEventsHaveStartedNewGesture(false),
57 mRegisteredWithRefreshDriver(false) {
58 SendSwipeEvent(eSwipeGestureStart, 0, 0.0, aSwipeStartEvent.mTimeStamp);
59 ProcessEvent(aSwipeStartEvent, /* aProcessingFirstEvent = */ true);
62 void SwipeTracker::Destroy() { UnregisterFromRefreshDriver(); }
64 SwipeTracker::~SwipeTracker() {
65 MOZ_ASSERT(!mRegisteredWithRefreshDriver,
66 "Destroy needs to be called before deallocating");
69 double SwipeTracker::SwipeSuccessTargetValue() const {
70 return (mSwipeDirection == dom::SimpleGestureEvent_Binding::DIRECTION_RIGHT)
71 ? -1.0
72 : 1.0;
75 double SwipeTracker::ClampToAllowedRange(double aGestureAmount) const {
76 // gestureAmount needs to stay between -1 and 0 when swiping right and
77 // between 0 and 1 when swiping left.
78 double min =
79 (mSwipeDirection == dom::SimpleGestureEvent_Binding::DIRECTION_RIGHT)
80 ? -1.0
81 : 0.0;
82 double max =
83 (mSwipeDirection == dom::SimpleGestureEvent_Binding::DIRECTION_LEFT)
84 ? 1.0
85 : 0.0;
86 return clamped(aGestureAmount, min, max);
89 bool SwipeTracker::ComputeSwipeSuccess() const {
90 double targetValue = SwipeSuccessTargetValue();
92 // If the fingers were moving away from the target direction when they were
93 // lifted from the touchpad, abort the swipe.
94 if (mCurrentVelocity * targetValue <
95 -StaticPrefs::widget_swipe_velocity_twitch_tolerance()) {
96 return false;
99 return (mGestureAmount * targetValue +
100 mCurrentVelocity * targetValue *
101 StaticPrefs::widget_swipe_success_velocity_contribution()) >=
102 kSwipeSuccessThreshold;
105 nsEventStatus SwipeTracker::ProcessEvent(
106 const PanGestureInput& aEvent, bool aProcessingFirstEvent /* = false */) {
107 // If the fingers have already been lifted or the swipe direction is where
108 // navigation is impossible, don't process this event for swiping.
109 if (!mEventsAreControllingSwipe || !SwipingInAllowedDirection()) {
110 // Return nsEventStatus_eConsumeNoDefault for events from the swipe gesture
111 // and nsEventStatus_eIgnore for events of subsequent scroll gestures.
112 if (aEvent.mType == PanGestureInput::PANGESTURE_MAYSTART ||
113 aEvent.mType == PanGestureInput::PANGESTURE_START) {
114 mEventsHaveStartedNewGesture = true;
116 return mEventsHaveStartedNewGesture ? nsEventStatus_eIgnore
117 : nsEventStatus_eConsumeNoDefault;
120 double delta = -aEvent.mPanDisplacement.x /
121 mWidget.GetDefaultScaleInternal() /
122 StaticPrefs::widget_swipe_whole_page_pixel_size();
123 mGestureAmount = ClampToAllowedRange(mGestureAmount + delta);
124 if (aEvent.mType != PanGestureInput::PANGESTURE_END) {
125 if (!aProcessingFirstEvent) {
126 double elapsedSeconds = std::max(
127 0.008, (aEvent.mTimeStamp - mLastEventTimeStamp).ToSeconds());
128 mCurrentVelocity = delta / elapsedSeconds;
130 mLastEventTimeStamp = aEvent.mTimeStamp;
133 const bool computedSwipeSuccess = ComputeSwipeSuccess();
134 double eventAmount = mGestureAmount;
135 // If ComputeSwipeSuccess returned false because the users fingers were
136 // moving slightly away from the target direction then we do not want to
137 // display the UI as if we were at the success threshold as that would
138 // give a false indication that navigation would happen.
139 if (!computedSwipeSuccess && (eventAmount >= kSwipeSuccessThreshold ||
140 eventAmount <= -kSwipeSuccessThreshold)) {
141 eventAmount = 0.999 * kSwipeSuccessThreshold;
142 if (mGestureAmount < 0.f) {
143 eventAmount = -eventAmount;
147 SendSwipeEvent(eSwipeGestureUpdate, 0, eventAmount, aEvent.mTimeStamp);
149 if (aEvent.mType == PanGestureInput::PANGESTURE_END) {
150 mEventsAreControllingSwipe = false;
151 if (computedSwipeSuccess) {
152 // Let's use same timestamp as previous event because this is caused by
153 // the preceding event.
154 SendSwipeEvent(eSwipeGesture, mSwipeDirection, 0.0, aEvent.mTimeStamp);
155 UnregisterFromRefreshDriver();
156 NS_DispatchToMainThread(
157 NS_NewRunnableFunction("SwipeTracker::SwipeFinished",
158 [swipeTracker = RefPtr<SwipeTracker>(this),
159 timeStamp = aEvent.mTimeStamp] {
160 swipeTracker->SwipeFinished(timeStamp);
161 }));
162 } else {
163 StartAnimating(eventAmount, 0.0);
167 return nsEventStatus_eConsumeNoDefault;
170 void SwipeTracker::StartAnimating(double aStartValue, double aTargetValue) {
171 mAxis.SetPosition(aStartValue);
172 mAxis.SetDestination(aTargetValue);
173 mAxis.SetVelocity(mCurrentVelocity);
175 mLastAnimationFrameTime = TimeStamp::Now();
177 // Add ourselves as a refresh driver observer. The refresh driver
178 // will call WillRefresh for each animation frame until we
179 // unregister ourselves.
180 MOZ_ASSERT(!mRegisteredWithRefreshDriver);
181 if (mRefreshDriver) {
182 mRefreshDriver->AddRefreshObserver(this, FlushType::Style,
183 "Swipe animation");
184 mRegisteredWithRefreshDriver = true;
188 void SwipeTracker::WillRefresh(mozilla::TimeStamp aTime) {
189 TimeStamp now = TimeStamp::Now();
190 mAxis.Simulate(now - mLastAnimationFrameTime);
191 mLastAnimationFrameTime = now;
193 bool isFinished =
194 mAxis.IsFinished(1.0 / StaticPrefs::widget_swipe_whole_page_pixel_size());
195 mGestureAmount = (isFinished ? mAxis.GetDestination() : mAxis.GetPosition());
196 SendSwipeEvent(eSwipeGestureUpdate, 0, mGestureAmount, now);
198 if (isFinished) {
199 UnregisterFromRefreshDriver();
200 SwipeFinished(now);
204 void SwipeTracker::CancelSwipe(const TimeStamp& aTimeStamp) {
205 SendSwipeEvent(eSwipeGestureEnd, 0, 0.0, aTimeStamp);
208 void SwipeTracker::SwipeFinished(const TimeStamp& aTimeStamp) {
209 SendSwipeEvent(eSwipeGestureEnd, 0, 0.0, aTimeStamp);
210 mWidget.SwipeFinished();
213 void SwipeTracker::UnregisterFromRefreshDriver() {
214 if (mRegisteredWithRefreshDriver) {
215 MOZ_ASSERT(mRefreshDriver, "How were we able to register, then?");
216 mRefreshDriver->RemoveRefreshObserver(this, FlushType::Style);
218 mRegisteredWithRefreshDriver = false;
221 /* static */ WidgetSimpleGestureEvent SwipeTracker::CreateSwipeGestureEvent(
222 EventMessage aMsg, nsIWidget* aWidget,
223 const LayoutDeviceIntPoint& aPosition, const TimeStamp& aTimeStamp) {
224 // XXX Why isn't this initialized with nsCocoaUtils::InitInputEvent()?
225 WidgetSimpleGestureEvent geckoEvent(true, aMsg, aWidget);
226 geckoEvent.mModifiers = 0;
227 // XXX How about geckoEvent.mTime?
228 geckoEvent.mTimeStamp = aTimeStamp;
229 geckoEvent.mRefPoint = aPosition;
230 geckoEvent.mButtons = 0;
231 return geckoEvent;
234 bool SwipeTracker::SendSwipeEvent(EventMessage aMsg, uint32_t aDirection,
235 double aDelta, const TimeStamp& aTimeStamp) {
236 WidgetSimpleGestureEvent geckoEvent =
237 CreateSwipeGestureEvent(aMsg, &mWidget, mEventPosition, aTimeStamp);
238 geckoEvent.mDirection = aDirection;
239 geckoEvent.mDelta = aDelta;
240 geckoEvent.mAllowedDirections = mAllowedDirections;
241 return mWidget.DispatchWindowEvent(geckoEvent);
244 // static
245 bool SwipeTracker::CanTriggerSwipe(const PanGestureInput& aPanInput) {
246 if (StaticPrefs::widget_disable_swipe_tracker()) {
247 return false;
250 if (aPanInput.mType != PanGestureInput::PANGESTURE_START) {
251 return false;
254 // Only initiate horizontal tracking for events whose horizontal element is
255 // at least eight times larger than its vertical element. This minimizes
256 // performance problems with vertical scrolls (by minimizing the possibility
257 // that they'll be misinterpreted as horizontal swipes), while still
258 // tolerating a small vertical element to a true horizontal swipe. The number
259 // '8' was arrived at by trial and error.
260 return std::abs(aPanInput.mPanDisplacement.x) >
261 std::abs(aPanInput.mPanDisplacement.y) * 8;
264 } // namespace mozilla