Bug 1472338: part 1) Add Chrome tests for the async Clipboard API. r=NeilDeakin
[gecko.git] / widget / SwipeTracker.cpp
blobb96979c46363ea2926ff653bd7fa57476a4d83b4
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/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
22 // swipe.
23 static const double kSpringForce = 250.0;
24 static const double kWholePagePixelSize = 550.0;
26 namespace mozilla {
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)
43 : mWidget(aWidget),
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),
52 mGestureAmount(0.0),
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)
70 ? -1.0
71 : 1.0;
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.
77 double min =
78 (mSwipeDirection == dom::SimpleGestureEvent_Binding::DIRECTION_RIGHT)
79 ? -1.0
80 : 0.0;
81 double max =
82 (mSwipeDirection == dom::SimpleGestureEvent_Binding::DIRECTION_LEFT)
83 ? 1.0
84 : 0.0;
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()) {
95 return false;
98 return (mGestureAmount * targetValue +
99 mCurrentVelocity * targetValue *
100 StaticPrefs::widget_swipe_success_velocity_contribution()) >=
102 StaticPrefs::widget_swipe_success_threshold();
105 nsEventStatus SwipeTracker::ProcessEvent(const PanGestureInput& aEvent) {
106 // If the fingers have already been lifted or the swipe direction is where
107 // navigation is impossible, don't process this event for swiping.
108 if (!mEventsAreControllingSwipe || !SwipingInAllowedDirection()) {
109 // Return nsEventStatus_eConsumeNoDefault for events from the swipe gesture
110 // and nsEventStatus_eIgnore for events of subsequent scroll gestures.
111 if (aEvent.mType == PanGestureInput::PANGESTURE_MAYSTART ||
112 aEvent.mType == PanGestureInput::PANGESTURE_START) {
113 mEventsHaveStartedNewGesture = true;
115 return mEventsHaveStartedNewGesture ? nsEventStatus_eIgnore
116 : nsEventStatus_eConsumeNoDefault;
119 double delta = -aEvent.mPanDisplacement.x /
120 mWidget.GetDefaultScaleInternal() / kWholePagePixelSize;
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;
129 } else {
130 mEventsAreControllingSwipe = false;
131 double targetValue = 0.0;
132 if (ComputeSwipeSuccess()) {
133 // Let's use same timestamp as previous event because this is caused by
134 // the preceding event.
135 SendSwipeEvent(eSwipeGesture, mSwipeDirection, 0.0, aEvent.mTimeStamp);
136 targetValue = SwipeSuccessTargetValue();
138 StartAnimating(targetValue);
141 return nsEventStatus_eConsumeNoDefault;
144 void SwipeTracker::StartAnimating(double aTargetValue) {
145 mAxis.SetPosition(mGestureAmount);
146 mAxis.SetDestination(aTargetValue);
147 mAxis.SetVelocity(mCurrentVelocity);
149 mLastAnimationFrameTime = TimeStamp::Now();
151 // Add ourselves as a refresh driver observer. The refresh driver
152 // will call WillRefresh for each animation frame until we
153 // unregister ourselves.
154 MOZ_ASSERT(!mRegisteredWithRefreshDriver);
155 if (mRefreshDriver) {
156 mRefreshDriver->AddRefreshObserver(this, FlushType::Style,
157 "Swipe animation");
158 mRegisteredWithRefreshDriver = true;
162 void SwipeTracker::WillRefresh(mozilla::TimeStamp aTime) {
163 TimeStamp now = TimeStamp::Now();
164 mAxis.Simulate(now - mLastAnimationFrameTime);
165 mLastAnimationFrameTime = now;
167 bool isFinished = mAxis.IsFinished(1.0 / kWholePagePixelSize);
168 mGestureAmount = (isFinished ? mAxis.GetDestination() : mAxis.GetPosition());
169 SendSwipeEvent(eSwipeGestureUpdate, 0, mGestureAmount, now);
171 if (isFinished) {
172 UnregisterFromRefreshDriver();
173 SwipeFinished(now);
177 void SwipeTracker::CancelSwipe(const TimeStamp& aTimeStamp) {
178 SendSwipeEvent(eSwipeGestureEnd, 0, 0.0, aTimeStamp);
181 void SwipeTracker::SwipeFinished(const TimeStamp& aTimeStamp) {
182 SendSwipeEvent(eSwipeGestureEnd, 0, 0.0, aTimeStamp);
183 mWidget.SwipeFinished();
186 void SwipeTracker::UnregisterFromRefreshDriver() {
187 if (mRegisteredWithRefreshDriver) {
188 MOZ_ASSERT(mRefreshDriver, "How were we able to register, then?");
189 mRefreshDriver->RemoveRefreshObserver(this, FlushType::Style);
191 mRegisteredWithRefreshDriver = false;
194 /* static */ WidgetSimpleGestureEvent SwipeTracker::CreateSwipeGestureEvent(
195 EventMessage aMsg, nsIWidget* aWidget,
196 const LayoutDeviceIntPoint& aPosition, const TimeStamp& aTimeStamp) {
197 // XXX Why isn't this initialized with nsCocoaUtils::InitInputEvent()?
198 WidgetSimpleGestureEvent geckoEvent(true, aMsg, aWidget);
199 geckoEvent.mModifiers = 0;
200 // XXX How about geckoEvent.mTime?
201 geckoEvent.mTimeStamp = aTimeStamp;
202 geckoEvent.mRefPoint = aPosition;
203 geckoEvent.mButtons = 0;
204 return geckoEvent;
207 bool SwipeTracker::SendSwipeEvent(EventMessage aMsg, uint32_t aDirection,
208 double aDelta, const TimeStamp& aTimeStamp) {
209 WidgetSimpleGestureEvent geckoEvent =
210 CreateSwipeGestureEvent(aMsg, &mWidget, mEventPosition, aTimeStamp);
211 geckoEvent.mDirection = aDirection;
212 geckoEvent.mDelta = aDelta;
213 geckoEvent.mAllowedDirections = mAllowedDirections;
214 return mWidget.DispatchWindowEvent(geckoEvent);
217 // static
218 bool SwipeTracker::CanTriggerSwipe(const PanGestureInput& aPanInput) {
219 if (StaticPrefs::widget_disable_swipe_tracker()) {
220 return false;
223 if (aPanInput.mType != PanGestureInput::PANGESTURE_START) {
224 return false;
227 // Only initiate horizontal tracking for events whose horizontal element is
228 // at least eight times larger than its vertical element. This minimizes
229 // performance problems with vertical scrolls (by minimizing the possibility
230 // that they'll be misinterpreted as horizontal swipes), while still
231 // tolerating a small vertical element to a true horizontal swipe. The number
232 // '8' was arrived at by trial and error.
233 return std::abs(aPanInput.mPanDisplacement.x) >
234 std::abs(aPanInput.mPanDisplacement.y) * 8;
237 } // namespace mozilla