2015-08-27 23:07:59 +03:00
|
|
|
/* -*- 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/. */
|
|
|
|
|
|
|
|
#include "SwipeTracker.h"
|
|
|
|
|
|
|
|
#include "InputData.h"
|
2017-01-06 10:29:47 +03:00
|
|
|
#include "mozilla/FlushType.h"
|
2015-08-27 23:07:59 +03:00
|
|
|
#include "mozilla/TimeStamp.h"
|
|
|
|
#include "mozilla/TouchEvents.h"
|
|
|
|
#include "nsAlgorithm.h"
|
|
|
|
#include "nsChildView.h"
|
|
|
|
#include "UnitTransforms.h"
|
|
|
|
|
|
|
|
// These values were tweaked to make the physics feel similar to the native swipe.
|
|
|
|
static const double kSpringForce = 250.0;
|
|
|
|
static const double kVelocityTwitchTolerance = 0.0000001;
|
|
|
|
static const double kWholePagePixelSize = 1000.0;
|
|
|
|
static const double kRubberBandResistanceFactor = 4.0;
|
|
|
|
static const double kSwipeSuccessThreshold = 0.25;
|
|
|
|
static const double kSwipeSuccessVelocityContribution = 0.3;
|
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
|
|
|
|
static already_AddRefed<nsRefreshDriver>
|
|
|
|
GetRefreshDriver(nsIWidget& aWidget)
|
|
|
|
{
|
|
|
|
nsIWidgetListener* widgetListener = aWidget.GetWidgetListener();
|
|
|
|
nsIPresShell* presShell = widgetListener ? widgetListener->GetPresShell() : nullptr;
|
|
|
|
nsPresContext* presContext = presShell ? presShell->GetPresContext() : nullptr;
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<nsRefreshDriver> refreshDriver = presContext ? presContext->RefreshDriver() : nullptr;
|
2015-08-27 23:07:59 +03:00
|
|
|
return refreshDriver.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
SwipeTracker::SwipeTracker(nsChildView& aWidget,
|
|
|
|
const PanGestureInput& aSwipeStartEvent,
|
|
|
|
uint32_t aAllowedDirections,
|
|
|
|
uint32_t aSwipeDirection)
|
|
|
|
: mWidget(aWidget)
|
|
|
|
, mRefreshDriver(GetRefreshDriver(mWidget))
|
|
|
|
, mAxis(0.0, 0.0, 0.0, kSpringForce, 1.0)
|
|
|
|
, mEventPosition(RoundedToInt(ViewAs<LayoutDevicePixel>(aSwipeStartEvent.mPanStartPoint,
|
2015-11-19 03:32:37 +03:00
|
|
|
PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent)))
|
2015-08-27 23:07:59 +03:00
|
|
|
, mLastEventTimeStamp(aSwipeStartEvent.mTimeStamp)
|
|
|
|
, mAllowedDirections(aAllowedDirections)
|
|
|
|
, mSwipeDirection(aSwipeDirection)
|
|
|
|
, mGestureAmount(0.0)
|
|
|
|
, mCurrentVelocity(0.0)
|
|
|
|
, mEventsAreControllingSwipe(true)
|
|
|
|
, mEventsHaveStartedNewGesture(false)
|
|
|
|
, mRegisteredWithRefreshDriver(false)
|
|
|
|
{
|
2016-12-20 14:58:04 +03:00
|
|
|
SendSwipeEvent(eSwipeGestureStart, 0, 0.0, aSwipeStartEvent.mTimeStamp);
|
2015-08-27 23:07:59 +03:00
|
|
|
ProcessEvent(aSwipeStartEvent);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
SwipeTracker::Destroy()
|
|
|
|
{
|
|
|
|
UnregisterFromRefreshDriver();
|
|
|
|
}
|
|
|
|
|
|
|
|
SwipeTracker::~SwipeTracker()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(!mRegisteredWithRefreshDriver, "Destroy needs to be called before deallocating");
|
|
|
|
}
|
|
|
|
|
|
|
|
double
|
|
|
|
SwipeTracker::SwipeSuccessTargetValue() const
|
|
|
|
{
|
|
|
|
return (mSwipeDirection == nsIDOMSimpleGestureEvent::DIRECTION_RIGHT) ? -1.0 : 1.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
double
|
|
|
|
SwipeTracker::ClampToAllowedRange(double aGestureAmount) const
|
|
|
|
{
|
|
|
|
// gestureAmount needs to stay between -1 and 0 when swiping right and
|
|
|
|
// between 0 and 1 when swiping left.
|
|
|
|
double min = (mSwipeDirection == nsIDOMSimpleGestureEvent::DIRECTION_RIGHT) ? -1.0 : 0.0;
|
|
|
|
double max = (mSwipeDirection == nsIDOMSimpleGestureEvent::DIRECTION_LEFT) ? 1.0 : 0.0;
|
|
|
|
return clamped(aGestureAmount, min, max);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
SwipeTracker::ComputeSwipeSuccess() const
|
|
|
|
{
|
|
|
|
double targetValue = SwipeSuccessTargetValue();
|
|
|
|
|
|
|
|
// If the fingers were moving away from the target direction when they were
|
|
|
|
// lifted from the touchpad, abort the swipe.
|
|
|
|
if (mCurrentVelocity * targetValue < -kVelocityTwitchTolerance) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (mGestureAmount * targetValue +
|
|
|
|
mCurrentVelocity * targetValue * kSwipeSuccessVelocityContribution) >= kSwipeSuccessThreshold;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsEventStatus
|
|
|
|
SwipeTracker::ProcessEvent(const PanGestureInput& aEvent)
|
|
|
|
{
|
|
|
|
// If the fingers have already been lifted, don't process this event for swiping.
|
|
|
|
if (!mEventsAreControllingSwipe) {
|
|
|
|
// Return nsEventStatus_eConsumeNoDefault for events from the swipe gesture
|
|
|
|
// and nsEventStatus_eIgnore for events of subsequent scroll gestures.
|
|
|
|
if (aEvent.mType == PanGestureInput::PANGESTURE_MAYSTART ||
|
|
|
|
aEvent.mType == PanGestureInput::PANGESTURE_START) {
|
|
|
|
mEventsHaveStartedNewGesture = true;
|
|
|
|
}
|
|
|
|
return mEventsHaveStartedNewGesture ? nsEventStatus_eIgnore : nsEventStatus_eConsumeNoDefault;
|
|
|
|
}
|
|
|
|
|
|
|
|
double delta = -aEvent.mPanDisplacement.x / mWidget.BackingScaleFactor() / kWholePagePixelSize;
|
|
|
|
if (!SwipingInAllowedDirection()) {
|
|
|
|
delta /= kRubberBandResistanceFactor;
|
|
|
|
}
|
|
|
|
mGestureAmount = ClampToAllowedRange(mGestureAmount + delta);
|
2016-12-20 14:58:04 +03:00
|
|
|
SendSwipeEvent(eSwipeGestureUpdate, 0, mGestureAmount, aEvent.mTimeStamp);
|
2015-08-27 23:07:59 +03:00
|
|
|
|
|
|
|
if (aEvent.mType != PanGestureInput::PANGESTURE_END) {
|
|
|
|
double elapsedSeconds = std::max(0.008, (aEvent.mTimeStamp - mLastEventTimeStamp).ToSeconds());
|
|
|
|
mCurrentVelocity = delta / elapsedSeconds;
|
|
|
|
mLastEventTimeStamp = aEvent.mTimeStamp;
|
|
|
|
} else {
|
|
|
|
mEventsAreControllingSwipe = false;
|
|
|
|
bool didSwipeSucceed = SwipingInAllowedDirection() && ComputeSwipeSuccess();
|
|
|
|
double targetValue = 0.0;
|
|
|
|
if (didSwipeSucceed) {
|
2016-12-20 14:58:04 +03:00
|
|
|
// Let's use same timestamp as previous event because this is caused by
|
|
|
|
// the preceding event.
|
|
|
|
SendSwipeEvent(eSwipeGesture, mSwipeDirection, 0.0, aEvent.mTimeStamp);
|
2015-08-27 23:07:59 +03:00
|
|
|
targetValue = SwipeSuccessTargetValue();
|
|
|
|
}
|
|
|
|
StartAnimating(targetValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
return nsEventStatus_eConsumeNoDefault;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
SwipeTracker::StartAnimating(double aTargetValue)
|
|
|
|
{
|
|
|
|
mAxis.SetPosition(mGestureAmount);
|
|
|
|
mAxis.SetDestination(aTargetValue);
|
|
|
|
mAxis.SetVelocity(mCurrentVelocity);
|
|
|
|
|
|
|
|
mLastAnimationFrameTime = TimeStamp::Now();
|
|
|
|
|
|
|
|
// Add ourselves as a refresh driver observer. The refresh driver
|
|
|
|
// will call WillRefresh for each animation frame until we
|
|
|
|
// unregister ourselves.
|
|
|
|
MOZ_ASSERT(!mRegisteredWithRefreshDriver);
|
|
|
|
if (mRefreshDriver) {
|
2017-01-06 10:29:47 +03:00
|
|
|
mRefreshDriver->AddRefreshObserver(this, FlushType::Style);
|
2015-08-27 23:07:59 +03:00
|
|
|
mRegisteredWithRefreshDriver = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
SwipeTracker::WillRefresh(mozilla::TimeStamp aTime)
|
|
|
|
{
|
|
|
|
TimeStamp now = TimeStamp::Now();
|
|
|
|
mAxis.Simulate(now - mLastAnimationFrameTime);
|
|
|
|
mLastAnimationFrameTime = now;
|
|
|
|
|
|
|
|
bool isFinished = mAxis.IsFinished(1.0 / kWholePagePixelSize);
|
|
|
|
mGestureAmount = (isFinished ? mAxis.GetDestination() : mAxis.GetPosition());
|
2016-12-20 14:58:04 +03:00
|
|
|
SendSwipeEvent(eSwipeGestureUpdate, 0, mGestureAmount, now);
|
2015-08-27 23:07:59 +03:00
|
|
|
|
|
|
|
if (isFinished) {
|
|
|
|
UnregisterFromRefreshDriver();
|
2016-12-20 14:58:04 +03:00
|
|
|
SwipeFinished(now);
|
2015-08-27 23:07:59 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2016-12-20 14:58:04 +03:00
|
|
|
SwipeTracker::CancelSwipe(const TimeStamp& aTimeStamp)
|
2015-08-27 23:07:59 +03:00
|
|
|
{
|
2016-12-20 14:58:04 +03:00
|
|
|
SendSwipeEvent(eSwipeGestureEnd, 0, 0.0, aTimeStamp);
|
2015-08-27 23:07:59 +03:00
|
|
|
}
|
|
|
|
|
2016-12-20 14:58:04 +03:00
|
|
|
void SwipeTracker::SwipeFinished(const TimeStamp& aTimeStamp)
|
2015-08-27 23:07:59 +03:00
|
|
|
{
|
2016-12-20 14:58:04 +03:00
|
|
|
SendSwipeEvent(eSwipeGestureEnd, 0, 0.0, aTimeStamp);
|
2015-08-27 23:07:59 +03:00
|
|
|
mWidget.SwipeFinished();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
SwipeTracker::UnregisterFromRefreshDriver()
|
|
|
|
{
|
|
|
|
if (mRegisteredWithRefreshDriver) {
|
|
|
|
MOZ_ASSERT(mRefreshDriver, "How were we able to register, then?");
|
|
|
|
mRefreshDriver->RemoveRefreshObserver(this, Flush_Style);
|
|
|
|
}
|
|
|
|
mRegisteredWithRefreshDriver = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */ WidgetSimpleGestureEvent
|
|
|
|
SwipeTracker::CreateSwipeGestureEvent(EventMessage aMsg, nsIWidget* aWidget,
|
2016-12-20 14:58:04 +03:00
|
|
|
const LayoutDeviceIntPoint& aPosition,
|
|
|
|
const TimeStamp& aTimeStamp)
|
2015-08-27 23:07:59 +03:00
|
|
|
{
|
2016-12-20 14:58:04 +03:00
|
|
|
// XXX Why isn't this initialized with nsCocoaUtils::InitInputEvent()?
|
2015-08-27 23:07:59 +03:00
|
|
|
WidgetSimpleGestureEvent geckoEvent(true, aMsg, aWidget);
|
2016-03-31 11:03:00 +03:00
|
|
|
geckoEvent.mModifiers = 0;
|
2016-12-20 14:58:04 +03:00
|
|
|
// XXX How about geckoEvent.mTime?
|
|
|
|
geckoEvent.mTimeStamp = aTimeStamp;
|
2016-04-18 17:09:02 +03:00
|
|
|
geckoEvent.mRefPoint = aPosition;
|
2015-08-27 23:07:59 +03:00
|
|
|
geckoEvent.buttons = 0;
|
|
|
|
return geckoEvent;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2016-12-20 14:58:04 +03:00
|
|
|
SwipeTracker::SendSwipeEvent(EventMessage aMsg,
|
|
|
|
uint32_t aDirection,
|
|
|
|
double aDelta,
|
|
|
|
const TimeStamp& aTimeStamp)
|
2015-08-27 23:07:59 +03:00
|
|
|
{
|
|
|
|
WidgetSimpleGestureEvent geckoEvent =
|
2016-12-20 14:58:04 +03:00
|
|
|
CreateSwipeGestureEvent(aMsg, &mWidget, mEventPosition, aTimeStamp);
|
2016-05-09 22:16:54 +03:00
|
|
|
geckoEvent.mDirection = aDirection;
|
2016-05-09 22:16:55 +03:00
|
|
|
geckoEvent.mDelta = aDelta;
|
2016-05-09 22:16:54 +03:00
|
|
|
geckoEvent.mAllowedDirections = mAllowedDirections;
|
2015-08-27 23:07:59 +03:00
|
|
|
return mWidget.DispatchWindowEvent(geckoEvent);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace mozilla
|