зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
9439cfc6c5
Коммит
642d331b8c
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
#include "DirectManipulationOwner.h"
|
#include "DirectManipulationOwner.h"
|
||||||
#include "nsWindow.h"
|
#include "nsWindow.h"
|
||||||
|
#include "InputData.h"
|
||||||
|
#include "mozilla/TimeStamp.h"
|
||||||
|
|
||||||
// Direct Manipulation is only defined for Win8 and newer.
|
// Direct Manipulation is only defined for Win8 and newer.
|
||||||
#if defined(_WIN32_WINNT)
|
#if defined(_WIN32_WINNT)
|
||||||
|
@ -24,6 +26,8 @@ namespace widget {
|
||||||
class DManipEventHandler : public IDirectManipulationViewportEventHandler,
|
class DManipEventHandler : public IDirectManipulationViewportEventHandler,
|
||||||
public IDirectManipulationInteractionEventHandler {
|
public IDirectManipulationInteractionEventHandler {
|
||||||
public:
|
public:
|
||||||
|
typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
|
||||||
|
|
||||||
friend class DirectManipulationOwner;
|
friend class DirectManipulationOwner;
|
||||||
|
|
||||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler)
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler)
|
||||||
|
@ -33,8 +37,8 @@ class DManipEventHandler : public IDirectManipulationViewportEventHandler,
|
||||||
friend class DirectManipulationOwner;
|
friend class DirectManipulationOwner;
|
||||||
|
|
||||||
explicit DManipEventHandler(nsWindow* aWindow,
|
explicit DManipEventHandler(nsWindow* aWindow,
|
||||||
DirectManipulationOwner* aOwner)
|
DirectManipulationOwner* aOwner,
|
||||||
: mWindow(aWindow), mOwner(aOwner) {}
|
const LayoutDeviceIntRect& aBounds);
|
||||||
|
|
||||||
HRESULT STDMETHODCALLTYPE OnViewportStatusChanged(
|
HRESULT STDMETHODCALLTYPE OnViewportStatusChanged(
|
||||||
IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,
|
IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,
|
||||||
|
@ -70,14 +74,38 @@ class DManipEventHandler : public IDirectManipulationViewportEventHandler,
|
||||||
DManipEventHandler* mOwner;
|
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:
|
private:
|
||||||
virtual ~DManipEventHandler() = default;
|
virtual ~DManipEventHandler() = default;
|
||||||
|
|
||||||
nsWindow* mWindow;
|
nsWindow* mWindow;
|
||||||
DirectManipulationOwner* mOwner;
|
DirectManipulationOwner* mOwner;
|
||||||
RefPtr<VObserver> mObserver;
|
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
|
STDMETHODIMP
|
||||||
DManipEventHandler::QueryInterface(REFIID iid, void** ppv) {
|
DManipEventHandler::QueryInterface(REFIID iid, void** ppv) {
|
||||||
const IID IID_IDirectManipulationViewportEventHandler =
|
const IID IID_IDirectManipulationViewportEventHandler =
|
||||||
|
@ -104,6 +132,45 @@ HRESULT
|
||||||
DManipEventHandler::OnViewportStatusChanged(
|
DManipEventHandler::OnViewportStatusChanged(
|
||||||
IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,
|
IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,
|
||||||
DIRECTMANIPULATION_STATUS previous) {
|
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;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,9 +179,127 @@ DManipEventHandler::OnViewportUpdated(IDirectManipulationViewport* viewport) {
|
||||||
return S_OK;
|
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
|
HRESULT
|
||||||
DManipEventHandler::OnContentUpdated(IDirectManipulationViewport* viewport,
|
DManipEventHandler::OnContentUpdated(IDirectManipulationViewport* viewport,
|
||||||
IDirectManipulationContent* content) {
|
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;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +342,103 @@ DirectManipulationOwner::DirectManipulationOwner(nsWindow* aWindow)
|
||||||
|
|
||||||
DirectManipulationOwner::~DirectManipulationOwner() { Destroy(); }
|
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) {
|
void DirectManipulationOwner::Init(const LayoutDeviceIntRect& aBounds) {
|
||||||
HRESULT hr = CoCreateInstance(
|
HRESULT hr = CoCreateInstance(
|
||||||
CLSID_DirectManipulationManager, nullptr, CLSCTX_INPROC_SERVER,
|
CLSID_DirectManipulationManager, nullptr, CLSCTX_INPROC_SERVER,
|
||||||
|
@ -218,7 +500,7 @@ void DirectManipulationOwner::Init(const LayoutDeviceIntRect& aBounds) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mDmHandler = new DManipEventHandler(mWindow, this);
|
mDmHandler = new DManipEventHandler(mWindow, this, aBounds);
|
||||||
|
|
||||||
hr = mDmViewport->AddEventHandler(wnd, mDmHandler.get(),
|
hr = mDmViewport->AddEventHandler(wnd, mDmHandler.get(),
|
||||||
&mDmViewportHandlerCookie);
|
&mDmViewportHandlerCookie);
|
||||||
|
@ -275,6 +557,10 @@ void DirectManipulationOwner::Init(const LayoutDeviceIntRect& aBounds) {
|
||||||
|
|
||||||
void DirectManipulationOwner::ResizeViewport(
|
void DirectManipulationOwner::ResizeViewport(
|
||||||
const LayoutDeviceIntRect& aBounds) {
|
const LayoutDeviceIntRect& aBounds) {
|
||||||
|
if (mDmHandler) {
|
||||||
|
mDmHandler->mBounds = aBounds;
|
||||||
|
}
|
||||||
|
|
||||||
if (mDmViewport) {
|
if (mDmViewport) {
|
||||||
RECT rect = {0, 0, aBounds.Width(), aBounds.Height()};
|
RECT rect = {0, 0, aBounds.Width(), aBounds.Height()};
|
||||||
HRESULT hr = mDmViewport->SetViewportRect(&rect);
|
HRESULT hr = mDmViewport->SetViewportRect(&rect);
|
||||||
|
|
|
@ -716,6 +716,16 @@ static bool ShouldCacheTitleBarInfo(nsWindowType aWindowType,
|
||||||
!nsUXThemeData::sTitlebarInfoPopulatedAero);
|
!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() {
|
void nsWindow::RecreateDirectManipulationIfNeeded() {
|
||||||
DestroyDirectManipulation();
|
DestroyDirectManipulation();
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,8 @@ class nsWindow final : public nsWindowBase {
|
||||||
|
|
||||||
friend class nsWindowGfx;
|
friend class nsWindowGfx;
|
||||||
|
|
||||||
|
void SendAnAPZEvent(mozilla::InputData& aEvent);
|
||||||
|
|
||||||
// nsWindowBase
|
// nsWindowBase
|
||||||
virtual void InitEvent(mozilla::WidgetGUIEvent& aEvent,
|
virtual void InitEvent(mozilla::WidgetGUIEvent& aEvent,
|
||||||
LayoutDeviceIntPoint* aPoint = nullptr) override;
|
LayoutDeviceIntPoint* aPoint = nullptr) override;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче