Bug 1630912. Add a small state machine and code to send pinch and pan events from direct manipulation. r=kats

We can't just get pinch events, we need to handle both.

This state machine code is basically copied from Chrome's implementation.

Differential Revision: https://phabricator.services.mozilla.com/D71307
This commit is contained in:
Timothy Nikkel 2020-05-08 23:20:23 +00:00
Родитель 9439cfc6c5
Коммит 642d331b8c
3 изменённых файлов: 301 добавлений и 3 удалений

Просмотреть файл

@ -5,6 +5,8 @@
#include "DirectManipulationOwner.h"
#include "nsWindow.h"
#include "InputData.h"
#include "mozilla/TimeStamp.h"
// Direct Manipulation is only defined for Win8 and newer.
#if defined(_WIN32_WINNT)
@ -24,6 +26,8 @@ namespace widget {
class DManipEventHandler : public IDirectManipulationViewportEventHandler,
public IDirectManipulationInteractionEventHandler {
public:
typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
friend class DirectManipulationOwner;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler)
@ -33,8 +37,8 @@ class DManipEventHandler : public IDirectManipulationViewportEventHandler,
friend class DirectManipulationOwner;
explicit DManipEventHandler(nsWindow* aWindow,
DirectManipulationOwner* aOwner)
: mWindow(aWindow), mOwner(aOwner) {}
DirectManipulationOwner* aOwner,
const LayoutDeviceIntRect& aBounds);
HRESULT STDMETHODCALLTYPE OnViewportStatusChanged(
IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,
@ -70,14 +74,38 @@ class DManipEventHandler : public IDirectManipulationViewportEventHandler,
DManipEventHandler* mOwner;
};
enum class State { eNone, ePanning, eInertia, ePinching };
void TransitionToState(State aNewState);
enum class Phase { eStart, eMiddle, eEnd };
void SendPinch(Phase aPhase, float aScale);
void SendPan(Phase aPhase, float x, float y, bool aIsInertia);
private:
virtual ~DManipEventHandler() = default;
nsWindow* mWindow;
DirectManipulationOwner* mOwner;
RefPtr<VObserver> mObserver;
float mLastScale;
float mLastXOffset;
float mLastYOffset;
LayoutDeviceIntRect mBounds;
bool mShouldSendPanStart;
State mState = State::eNone;
};
DManipEventHandler::DManipEventHandler(nsWindow* aWindow,
DirectManipulationOwner* aOwner,
const LayoutDeviceIntRect& aBounds)
: mWindow(aWindow),
mOwner(aOwner),
mLastScale(1.f),
mLastXOffset(0.f),
mLastYOffset(0.f),
mBounds(aBounds),
mShouldSendPanStart(false) {}
STDMETHODIMP
DManipEventHandler::QueryInterface(REFIID iid, void** ppv) {
const IID IID_IDirectManipulationViewportEventHandler =
@ -104,6 +132,45 @@ HRESULT
DManipEventHandler::OnViewportStatusChanged(
IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,
DIRECTMANIPULATION_STATUS previous) {
if (current == previous) {
return S_OK;
}
if (current == DIRECTMANIPULATION_INERTIA) {
if (previous != DIRECTMANIPULATION_RUNNING || mState != State::ePanning) {
// xxx transition to none?
return S_OK;
}
TransitionToState(State::eInertia);
}
if (current == DIRECTMANIPULATION_RUNNING) {
// INERTIA -> RUNNING, should start a new sequence.
if (previous == DIRECTMANIPULATION_INERTIA) {
TransitionToState(State::eNone);
}
}
if (current != DIRECTMANIPULATION_ENABLED &&
current != DIRECTMANIPULATION_READY) {
return S_OK;
}
// A session has ended, reset the transform.
if (mLastScale != 1.f || mLastXOffset != 0.f || mLastYOffset != 0.f) {
HRESULT hr =
viewport->ZoomToRect(0, 0, mBounds.width, mBounds.height, false);
if (!SUCCEEDED(hr)) {
NS_WARNING("ZoomToRect failed");
}
}
mLastScale = 1.f;
mLastXOffset = 0.f;
mLastYOffset = 0.f;
TransitionToState(State::eNone);
return S_OK;
}
@ -112,9 +179,127 @@ DManipEventHandler::OnViewportUpdated(IDirectManipulationViewport* viewport) {
return S_OK;
}
void DManipEventHandler::TransitionToState(State aNewState) {
if (mState == aNewState) {
return;
}
State prevState = mState;
mState = aNewState;
// End the previous sequence.
switch (prevState) {
case State::ePanning: {
// ePanning -> eNone, ePinching: PanEnd
// ePanning -> eInertia: we don't want to end the current scroll sequence.
if (aNewState != State::eInertia) {
SendPan(Phase::eEnd, 0.f, 0.f, false);
}
break;
}
case State::eInertia: {
// eInertia -> *: MomentumEnd
SendPan(Phase::eEnd, 0.f, 0.f, true);
break;
}
case State::ePinching: {
MOZ_ASSERT(aNewState == State::eNone);
// ePinching -> eNone: PinchEnd. ePinching should only transition to
// eNone.
SendPinch(Phase::eEnd, 0.f);
break;
}
case State::eNone: {
// eNone -> *: no cleanup is needed.
break;
}
default:
MOZ_ASSERT(false);
}
// Start the new sequence.
switch (aNewState) {
case State::ePanning: {
// eInertia, eNone -> ePanning: PanStart.
// We're being called from OnContentUpdated, it has the coords we need to
// pass to SendPan(Phase::eStart), so set mShouldSendPanStart and when we
// return OnContentUpdated will check it and call SendPan(Phase::eStart).
mShouldSendPanStart = true;
break;
}
case State::eInertia: {
// Only ePanning can transition to eInertia.
MOZ_ASSERT(prevState == State::ePanning);
SendPan(Phase::eStart, 0.f, 0.f, true);
break;
}
case State::ePinching: {
// * -> ePinching: PinchStart.
// Pinch gesture may begin with some scroll events.
SendPinch(Phase::eStart, 0.f);
break;
}
case State::eNone: {
// * -> eNone: only cleanup is needed.
break;
}
default:
MOZ_ASSERT(false);
}
}
HRESULT
DManipEventHandler::OnContentUpdated(IDirectManipulationViewport* viewport,
IDirectManipulationContent* content) {
float transform[6];
HRESULT hr = content->GetContentTransform(transform, ARRAYSIZE(transform));
if (!SUCCEEDED(hr)) {
NS_WARNING("GetContentTransform failed");
return S_OK;
}
float windowScale = mWindow ? mWindow->GetDefaultScale().scale : 1.f;
float scale = transform[0];
float xoffset = transform[4] * windowScale;
float yoffset = transform[5] * windowScale;
// Not different from last time.
if (FuzzyEqualsMultiplicative(scale, mLastScale) && xoffset == mLastXOffset &&
yoffset == mLastYOffset) {
return S_OK;
}
// Consider this is a Scroll when scale factor equals 1.0.
if (FuzzyEqualsMultiplicative(scale, 1.f)) {
if (mState == State::eNone || mState == State::eInertia) {
TransitionToState(State::ePanning);
}
} else {
// Pinch gesture may begin with some scroll events.
TransitionToState(State::ePinching);
}
if (mState == State::ePanning) {
if (mShouldSendPanStart) {
SendPan(Phase::eStart, mLastXOffset - xoffset, mLastYOffset - yoffset,
false);
mShouldSendPanStart = false;
} else {
SendPan(Phase::eMiddle, mLastXOffset - xoffset, mLastYOffset - yoffset,
false);
}
} else if (mState == State::eInertia) {
SendPan(Phase::eMiddle, mLastXOffset - xoffset, mLastYOffset - yoffset,
true);
} else if (mState == State::ePinching) {
SendPinch(Phase::eMiddle, scale);
}
mLastScale = scale;
mLastXOffset = xoffset;
mLastYOffset = yoffset;
return S_OK;
}
@ -157,6 +342,103 @@ DirectManipulationOwner::DirectManipulationOwner(nsWindow* aWindow)
DirectManipulationOwner::~DirectManipulationOwner() { Destroy(); }
void DManipEventHandler::SendPinch(Phase aPhase, float aScale) {
PinchGestureInput::PinchGestureType pinchGestureType =
PinchGestureInput::PINCHGESTURE_SCALE;
switch (aPhase) {
case Phase::eStart:
pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
break;
case Phase::eMiddle:
pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
break;
case Phase::eEnd:
pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
break;
default:
MOZ_ASSERT_UNREACHABLE("handle all enum values");
}
PRIntervalTime eventIntervalTime = PR_IntervalNow();
TimeStamp eventTimeStamp = TimeStamp::Now();
Modifiers mods =
MODIFIER_NONE; // xxx should we get getting key state for this?
ExternalPoint screenOffset = ViewAs<ExternalPixel>(
mWindow->WidgetToScreenOffset(),
PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
POINT cursor_pos;
::GetCursorPos(&cursor_pos);
ScreenPoint position = {(float)cursor_pos.x, (float)cursor_pos.y};
PinchGestureInput event{
pinchGestureType,
eventIntervalTime,
eventTimeStamp,
screenOffset,
position,
100.0 * ((aPhase != Phase::eMiddle) ? 1.f : aScale),
100.0 * ((aPhase != Phase::eMiddle) ? 1.f : mLastScale),
mods};
if (mWindow) {
mWindow->SendAnAPZEvent(event);
}
}
void DManipEventHandler::SendPan(Phase aPhase, float x, float y,
bool aIsInertia) {
PanGestureInput::PanGestureType panGestureType =
PanGestureInput::PANGESTURE_PAN;
if (aIsInertia) {
switch (aPhase) {
case Phase::eStart:
panGestureType = PanGestureInput::PANGESTURE_MOMENTUMSTART;
break;
case Phase::eMiddle:
panGestureType = PanGestureInput::PANGESTURE_MOMENTUMPAN;
break;
case Phase::eEnd:
panGestureType = PanGestureInput::PANGESTURE_MOMENTUMEND;
break;
default:
MOZ_ASSERT_UNREACHABLE("handle all enum values");
}
} else {
switch (aPhase) {
case Phase::eStart:
panGestureType = PanGestureInput::PANGESTURE_START;
break;
case Phase::eMiddle:
panGestureType = PanGestureInput::PANGESTURE_PAN;
break;
case Phase::eEnd:
panGestureType = PanGestureInput::PANGESTURE_END;
break;
default:
MOZ_ASSERT_UNREACHABLE("handle all enum values");
}
}
PRIntervalTime eventIntervalTime = PR_IntervalNow();
TimeStamp eventTimeStamp = TimeStamp::Now();
Modifiers mods = MODIFIER_NONE;
POINT cursor_pos;
::GetCursorPos(&cursor_pos);
ScreenPoint position = {(float)cursor_pos.x, (float)cursor_pos.y};
PanGestureInput event{panGestureType, eventIntervalTime, eventTimeStamp,
position, ScreenPoint(x, y), mods};
if (mWindow) {
mWindow->SendAnAPZEvent(event);
}
}
void DirectManipulationOwner::Init(const LayoutDeviceIntRect& aBounds) {
HRESULT hr = CoCreateInstance(
CLSID_DirectManipulationManager, nullptr, CLSCTX_INPROC_SERVER,
@ -218,7 +500,7 @@ void DirectManipulationOwner::Init(const LayoutDeviceIntRect& aBounds) {
return;
}
mDmHandler = new DManipEventHandler(mWindow, this);
mDmHandler = new DManipEventHandler(mWindow, this, aBounds);
hr = mDmViewport->AddEventHandler(wnd, mDmHandler.get(),
&mDmViewportHandlerCookie);
@ -275,6 +557,10 @@ void DirectManipulationOwner::Init(const LayoutDeviceIntRect& aBounds) {
void DirectManipulationOwner::ResizeViewport(
const LayoutDeviceIntRect& aBounds) {
if (mDmHandler) {
mDmHandler->mBounds = aBounds;
}
if (mDmViewport) {
RECT rect = {0, 0, aBounds.Width(), aBounds.Height()};
HRESULT hr = mDmViewport->SetViewportRect(&rect);

Просмотреть файл

@ -716,6 +716,16 @@ static bool ShouldCacheTitleBarInfo(nsWindowType aWindowType,
!nsUXThemeData::sTitlebarInfoPopulatedAero);
}
void nsWindow::SendAnAPZEvent(InputData& aEvent) {
if (mAPZC) {
APZEventResult es = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
// xxx look at es
}
// xxx dispatch regularly if we don't have an apzc or if apzc doesn't consume
// it, or don't create direct manip objects in the first place if we don't
// have an apzc?
}
void nsWindow::RecreateDirectManipulationIfNeeded() {
DestroyDirectManipulation();

Просмотреть файл

@ -111,6 +111,8 @@ class nsWindow final : public nsWindowBase {
friend class nsWindowGfx;
void SendAnAPZEvent(mozilla::InputData& aEvent);
// nsWindowBase
virtual void InitEvent(mozilla::WidgetGUIEvent& aEvent,
LayoutDeviceIntPoint* aPoint = nullptr) override;