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"
10 #include "mozilla/FlushType.h"
11 #include "mozilla/PresShell.h"
12 #include "mozilla/StaticPrefs_widget.h"
13 #include "mozilla/TimeStamp.h"
14 #include "mozilla/TouchEvents.h"
15 #include "mozilla/dom/SimpleGestureEventBinding.h"
16 #include "nsAlgorithm.h"
17 #include "nsIWidget.h"
18 #include "nsRefreshDriver.h"
19 #include "UnitTransforms.h"
21 // These values were tweaked to make the physics feel similar to the native
23 static const double kSpringForce
= 250.0;
24 static const double kSwipeSuccessThreshold
= 0.25;
28 static already_AddRefed
<nsRefreshDriver
> GetRefreshDriver(nsIWidget
& aWidget
) {
29 nsIWidgetListener
* widgetListener
= aWidget
.GetWidgetListener();
30 PresShell
* presShell
=
31 widgetListener
? widgetListener
->GetPresShell() : nullptr;
32 nsPresContext
* presContext
=
33 presShell
? presShell
->GetPresContext() : nullptr;
34 RefPtr
<nsRefreshDriver
> refreshDriver
=
35 presContext
? presContext
->RefreshDriver() : nullptr;
36 return refreshDriver
.forget();
39 SwipeTracker::SwipeTracker(nsIWidget
& aWidget
,
40 const PanGestureInput
& aSwipeStartEvent
,
41 uint32_t aAllowedDirections
,
42 uint32_t aSwipeDirection
)
44 mRefreshDriver(GetRefreshDriver(mWidget
)),
45 mAxis(0.0, 0.0, 0.0, kSpringForce
, 1.0),
46 mEventPosition(RoundedToInt(ViewAs
<LayoutDevicePixel
>(
47 aSwipeStartEvent
.mPanStartPoint
,
48 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent
))),
49 mLastEventTimeStamp(aSwipeStartEvent
.mTimeStamp
),
50 mAllowedDirections(aAllowedDirections
),
51 mSwipeDirection(aSwipeDirection
),
53 mCurrentVelocity(0.0),
54 mEventsAreControllingSwipe(true),
55 mEventsHaveStartedNewGesture(false),
56 mRegisteredWithRefreshDriver(false) {
57 SendSwipeEvent(eSwipeGestureStart
, 0, 0.0, aSwipeStartEvent
.mTimeStamp
);
58 ProcessEvent(aSwipeStartEvent
);
61 void SwipeTracker::Destroy() { UnregisterFromRefreshDriver(); }
63 SwipeTracker::~SwipeTracker() {
64 MOZ_ASSERT(!mRegisteredWithRefreshDriver
,
65 "Destroy needs to be called before deallocating");
68 double SwipeTracker::SwipeSuccessTargetValue() const {
69 return (mSwipeDirection
== dom::SimpleGestureEvent_Binding::DIRECTION_RIGHT
)
74 double SwipeTracker::ClampToAllowedRange(double aGestureAmount
) const {
75 // gestureAmount needs to stay between -1 and 0 when swiping right and
76 // between 0 and 1 when swiping left.
78 (mSwipeDirection
== dom::SimpleGestureEvent_Binding::DIRECTION_RIGHT
)
82 (mSwipeDirection
== dom::SimpleGestureEvent_Binding::DIRECTION_LEFT
)
85 return clamped(aGestureAmount
, min
, max
);
88 bool SwipeTracker::ComputeSwipeSuccess() const {
89 double targetValue
= SwipeSuccessTargetValue();
91 // If the fingers were moving away from the target direction when they were
92 // lifted from the touchpad, abort the swipe.
93 if (mCurrentVelocity
* targetValue
<
94 -StaticPrefs::widget_swipe_velocity_twitch_tolerance()) {
98 return (mGestureAmount
* targetValue
+
99 mCurrentVelocity
* targetValue
*
100 StaticPrefs::widget_swipe_success_velocity_contribution()) >=
101 kSwipeSuccessThreshold
;
104 nsEventStatus
SwipeTracker::ProcessEvent(const PanGestureInput
& aEvent
) {
105 // If the fingers have already been lifted or the swipe direction is where
106 // navigation is impossible, don't process this event for swiping.
107 if (!mEventsAreControllingSwipe
|| !SwipingInAllowedDirection()) {
108 // Return nsEventStatus_eConsumeNoDefault for events from the swipe gesture
109 // and nsEventStatus_eIgnore for events of subsequent scroll gestures.
110 if (aEvent
.mType
== PanGestureInput::PANGESTURE_MAYSTART
||
111 aEvent
.mType
== PanGestureInput::PANGESTURE_START
) {
112 mEventsHaveStartedNewGesture
= true;
114 return mEventsHaveStartedNewGesture
? nsEventStatus_eIgnore
115 : nsEventStatus_eConsumeNoDefault
;
118 double delta
= -aEvent
.mPanDisplacement
.x
/
119 mWidget
.GetDefaultScaleInternal() /
120 StaticPrefs::widget_swipe_whole_page_pixel_size();
121 mGestureAmount
= ClampToAllowedRange(mGestureAmount
+ delta
);
122 SendSwipeEvent(eSwipeGestureUpdate
, 0, mGestureAmount
, aEvent
.mTimeStamp
);
124 if (aEvent
.mType
!= PanGestureInput::PANGESTURE_END
) {
125 double elapsedSeconds
=
126 std::max(0.008, (aEvent
.mTimeStamp
- mLastEventTimeStamp
).ToSeconds());
127 mCurrentVelocity
= delta
/ elapsedSeconds
;
128 mLastEventTimeStamp
= aEvent
.mTimeStamp
;
130 mEventsAreControllingSwipe
= false;
131 if (ComputeSwipeSuccess()) {
132 // Let's use same timestamp as previous event because this is caused by
133 // the preceding event.
134 SendSwipeEvent(eSwipeGesture
, mSwipeDirection
, 0.0, aEvent
.mTimeStamp
);
135 UnregisterFromRefreshDriver();
136 NS_DispatchToMainThread(
137 NS_NewRunnableFunction("SwipeTracker::SwipeFinished",
138 [swipeTracker
= RefPtr
<SwipeTracker
>(this),
139 timeStamp
= aEvent
.mTimeStamp
] {
140 swipeTracker
->SwipeFinished(timeStamp
);
147 return nsEventStatus_eConsumeNoDefault
;
150 void SwipeTracker::StartAnimating(double aTargetValue
) {
151 mAxis
.SetPosition(mGestureAmount
);
152 mAxis
.SetDestination(aTargetValue
);
153 mAxis
.SetVelocity(mCurrentVelocity
);
155 mLastAnimationFrameTime
= TimeStamp::Now();
157 // Add ourselves as a refresh driver observer. The refresh driver
158 // will call WillRefresh for each animation frame until we
159 // unregister ourselves.
160 MOZ_ASSERT(!mRegisteredWithRefreshDriver
);
161 if (mRefreshDriver
) {
162 mRefreshDriver
->AddRefreshObserver(this, FlushType::Style
,
164 mRegisteredWithRefreshDriver
= true;
168 void SwipeTracker::WillRefresh(mozilla::TimeStamp aTime
) {
169 TimeStamp now
= TimeStamp::Now();
170 mAxis
.Simulate(now
- mLastAnimationFrameTime
);
171 mLastAnimationFrameTime
= now
;
174 mAxis
.IsFinished(1.0 / StaticPrefs::widget_swipe_whole_page_pixel_size());
175 mGestureAmount
= (isFinished
? mAxis
.GetDestination() : mAxis
.GetPosition());
176 SendSwipeEvent(eSwipeGestureUpdate
, 0, mGestureAmount
, now
);
179 UnregisterFromRefreshDriver();
184 void SwipeTracker::CancelSwipe(const TimeStamp
& aTimeStamp
) {
185 SendSwipeEvent(eSwipeGestureEnd
, 0, 0.0, aTimeStamp
);
188 void SwipeTracker::SwipeFinished(const TimeStamp
& aTimeStamp
) {
189 SendSwipeEvent(eSwipeGestureEnd
, 0, 0.0, aTimeStamp
);
190 mWidget
.SwipeFinished();
193 void SwipeTracker::UnregisterFromRefreshDriver() {
194 if (mRegisteredWithRefreshDriver
) {
195 MOZ_ASSERT(mRefreshDriver
, "How were we able to register, then?");
196 mRefreshDriver
->RemoveRefreshObserver(this, FlushType::Style
);
198 mRegisteredWithRefreshDriver
= false;
201 /* static */ WidgetSimpleGestureEvent
SwipeTracker::CreateSwipeGestureEvent(
202 EventMessage aMsg
, nsIWidget
* aWidget
,
203 const LayoutDeviceIntPoint
& aPosition
, const TimeStamp
& aTimeStamp
) {
204 // XXX Why isn't this initialized with nsCocoaUtils::InitInputEvent()?
205 WidgetSimpleGestureEvent
geckoEvent(true, aMsg
, aWidget
);
206 geckoEvent
.mModifiers
= 0;
207 // XXX How about geckoEvent.mTime?
208 geckoEvent
.mTimeStamp
= aTimeStamp
;
209 geckoEvent
.mRefPoint
= aPosition
;
210 geckoEvent
.mButtons
= 0;
214 bool SwipeTracker::SendSwipeEvent(EventMessage aMsg
, uint32_t aDirection
,
215 double aDelta
, const TimeStamp
& aTimeStamp
) {
216 WidgetSimpleGestureEvent geckoEvent
=
217 CreateSwipeGestureEvent(aMsg
, &mWidget
, mEventPosition
, aTimeStamp
);
218 geckoEvent
.mDirection
= aDirection
;
219 geckoEvent
.mDelta
= aDelta
;
220 geckoEvent
.mAllowedDirections
= mAllowedDirections
;
221 return mWidget
.DispatchWindowEvent(geckoEvent
);
225 bool SwipeTracker::CanTriggerSwipe(const PanGestureInput
& aPanInput
) {
226 if (StaticPrefs::widget_disable_swipe_tracker()) {
230 if (aPanInput
.mType
!= PanGestureInput::PANGESTURE_START
) {
234 // Only initiate horizontal tracking for events whose horizontal element is
235 // at least eight times larger than its vertical element. This minimizes
236 // performance problems with vertical scrolls (by minimizing the possibility
237 // that they'll be misinterpreted as horizontal swipes), while still
238 // tolerating a small vertical element to a true horizontal swipe. The number
239 // '8' was arrived at by trial and error.
240 return std::abs(aPanInput
.mPanDisplacement
.x
) >
241 std::abs(aPanInput
.mPanDisplacement
.y
) * 8;
244 } // namespace mozilla