gecko-dev/widget/windows/DirectManipulationOwner.cpp

733 строки
21 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "DirectManipulationOwner.h"
#include "nsWindow.h"
#include "WinModifierKeyState.h"
#include "InputData.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/SwipeTracker.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/VsyncDispatcher.h"
// Direct Manipulation is only defined for Win8 and newer.
#if defined(_WIN32_WINNT)
# undef _WIN32_WINNT
# define _WIN32_WINNT _WIN32_WINNT_WIN8
#endif // defined(_WIN32_WINNT)
#if defined(NTDDI_VERSION)
# undef NTDDI_VERSION
# define NTDDI_VERSION NTDDI_WIN8
#endif // defined(NTDDI_VERSION)
#include "directmanipulation.h"
namespace mozilla {
namespace widget {
class DManipEventHandler : public IDirectManipulationViewportEventHandler,
public IDirectManipulationInteractionEventHandler {
public:
typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
friend class DirectManipulationOwner;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler)
STDMETHODIMP QueryInterface(REFIID, void**) override;
friend class DirectManipulationOwner;
explicit DManipEventHandler(nsWindow* aWindow,
DirectManipulationOwner* aOwner,
const LayoutDeviceIntRect& aBounds);
HRESULT STDMETHODCALLTYPE OnViewportStatusChanged(
IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,
DIRECTMANIPULATION_STATUS previous) override;
HRESULT STDMETHODCALLTYPE
OnViewportUpdated(IDirectManipulationViewport* viewport) override;
HRESULT STDMETHODCALLTYPE
OnContentUpdated(IDirectManipulationViewport* viewport,
IDirectManipulationContent* content) override;
HRESULT STDMETHODCALLTYPE
OnInteraction(IDirectManipulationViewport2* viewport,
DIRECTMANIPULATION_INTERACTION_TYPE interaction) override;
void Update();
class VObserver final : public mozilla::VsyncObserver {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler::VObserver,
override)
public:
void NotifyVsync(const mozilla::VsyncEvent& aVsync) override {
if (mOwner) {
mOwner->Update();
}
}
explicit VObserver(DManipEventHandler* aOwner) : mOwner(aOwner) {}
void ClearOwner() { mOwner = nullptr; }
private:
virtual ~VObserver() {}
DManipEventHandler* mOwner;
};
enum class State { eNone, ePanning, eInertia, ePinching };
void TransitionToState(State aNewState);
enum class Phase { eStart, eMiddle, eEnd };
// Return value indicates if we sent an event or not and hence if we should
// update mLastScale. (We only want to send pinch events if the computed
// deltaY for the corresponding WidgetWheelEvent would be non-zero.)
bool SendPinch(Phase aPhase, float aScale);
void SendPan(Phase aPhase, float x, float y, bool aIsInertia);
static void SendPanCommon(nsWindow* aWindow, Phase aPhase,
ScreenPoint aPosition, double aDeltaX,
double aDeltaY, Modifiers aMods, bool aIsInertia);
static void SynthesizeNativeTouchpadPan(
nsWindow* aWindow, nsIWidget::TouchpadGesturePhase aEventPhase,
LayoutDeviceIntPoint aPoint, double aDeltaX, double aDeltaY,
int32_t aModifierFlags);
private:
virtual ~DManipEventHandler() = default;
nsWindow* mWindow;
DirectManipulationOwner* mOwner;
RefPtr<VObserver> mObserver;
float mLastScale;
float mLastXOffset;
float mLastYOffset;
LayoutDeviceIntRect mBounds;
bool mShouldSendPanStart;
bool mShouldSendPinchStart;
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),
mShouldSendPinchStart(false) {}
STDMETHODIMP
DManipEventHandler::QueryInterface(REFIID iid, void** ppv) {
const IID IID_IDirectManipulationViewportEventHandler =
__uuidof(IDirectManipulationViewportEventHandler);
const IID IID_IDirectManipulationInteractionEventHandler =
__uuidof(IDirectManipulationInteractionEventHandler);
if ((IID_IUnknown == iid) ||
(IID_IDirectManipulationViewportEventHandler == iid)) {
*ppv = static_cast<IDirectManipulationViewportEventHandler*>(this);
AddRef();
return S_OK;
}
if (IID_IDirectManipulationInteractionEventHandler == iid) {
*ppv = static_cast<IDirectManipulationInteractionEventHandler*>(this);
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
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;
}
HRESULT
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 -> *: PanEnd
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.
// Only send a pinch end if we sent a pinch start.
if (!mShouldSendPinchStart) {
SendPinch(Phase::eEnd, 0.f);
}
mShouldSendPinchStart = false;
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.
// We're being called from OnContentUpdated, it has the scale we need to
// pass to SendPinch(Phase::eStart), so set mShouldSendPinchStart and when
// we return OnContentUpdated will check it and call
// SendPinch(Phase::eStart).
mShouldSendPinchStart = true;
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 scale = transform[0];
float xoffset = transform[4];
float yoffset = transform[5];
// 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) {
TransitionToState(State::ePanning);
}
} else {
// Pinch gesture may begin with some scroll events.
TransitionToState(State::ePinching);
}
if (mState == State::ePanning || mState == State::eInertia) {
// Accumulate the offset (by not updating mLastX/YOffset) until we have at
// least one pixel.
float dx = std::abs(mLastXOffset - xoffset);
float dy = std::abs(mLastYOffset - yoffset);
if (dx < 1.f && dy < 1.f) {
return S_OK;
}
}
bool updateLastScale = true;
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) {
if (mShouldSendPinchStart) {
updateLastScale = SendPinch(Phase::eStart, scale);
// Only clear mShouldSendPinchStart if we actually sent the event
// (updateLastScale tells us if we sent an event).
if (updateLastScale) {
mShouldSendPinchStart = false;
}
} else {
updateLastScale = SendPinch(Phase::eMiddle, scale);
}
}
if (updateLastScale) {
mLastScale = scale;
}
mLastXOffset = xoffset;
mLastYOffset = yoffset;
return S_OK;
}
HRESULT
DManipEventHandler::OnInteraction(
IDirectManipulationViewport2* viewport,
DIRECTMANIPULATION_INTERACTION_TYPE interaction) {
if (interaction == DIRECTMANIPULATION_INTERACTION_BEGIN) {
if (!mObserver) {
mObserver = new VObserver(this);
}
gfxWindowsPlatform::GetPlatform()
->GetGlobalVsyncDispatcher()
->AddMainThreadObserver(mObserver);
}
if (mObserver && interaction == DIRECTMANIPULATION_INTERACTION_END) {
gfxWindowsPlatform::GetPlatform()
->GetGlobalVsyncDispatcher()
->RemoveMainThreadObserver(mObserver);
}
return S_OK;
}
void DManipEventHandler::Update() {
if (mOwner) {
mOwner->Update();
}
}
void DirectManipulationOwner::Update() {
if (mDmUpdateManager) {
mDmUpdateManager->Update(nullptr);
}
}
DirectManipulationOwner::DirectManipulationOwner(nsWindow* aWindow)
: mWindow(aWindow) {}
DirectManipulationOwner::~DirectManipulationOwner() { Destroy(); }
bool DManipEventHandler::SendPinch(Phase aPhase, float aScale) {
if (!mWindow) {
return false;
}
if (aScale == mLastScale && aPhase != Phase::eEnd) {
return false;
}
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");
}
TimeStamp eventTimeStamp = TimeStamp::Now();
ModifierKeyState modifierKeyState;
Modifiers mods = modifierKeyState.GetModifiers();
ExternalPoint screenOffset = ViewAs<ExternalPixel>(
mWindow->WidgetToScreenOffset(),
PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
POINT cursor_pos;
::GetCursorPos(&cursor_pos);
HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW));
::ScreenToClient(wnd, &cursor_pos);
ScreenPoint position = {(float)cursor_pos.x, (float)cursor_pos.y};
PinchGestureInput event{pinchGestureType,
PinchGestureInput::TRACKPAD,
eventTimeStamp,
screenOffset,
position,
100.0 * ((aPhase == Phase::eEnd) ? 1.f : aScale),
100.0 * ((aPhase == Phase::eEnd) ? 1.f : mLastScale),
mods};
if (!event.SetLineOrPageDeltaY(mWindow)) {
return false;
}
mWindow->SendAnAPZEvent(event);
return true;
}
void DManipEventHandler::SendPan(Phase aPhase, float x, float y,
bool aIsInertia) {
if (!mWindow) {
return;
}
ModifierKeyState modifierKeyState;
Modifiers mods = modifierKeyState.GetModifiers();
POINT cursor_pos;
::GetCursorPos(&cursor_pos);
HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW));
::ScreenToClient(wnd, &cursor_pos);
ScreenPoint position = {(float)cursor_pos.x, (float)cursor_pos.y};
SendPanCommon(mWindow, aPhase, position, x, y, mods, aIsInertia);
}
/* static */
void DManipEventHandler::SendPanCommon(nsWindow* aWindow, Phase aPhase,
ScreenPoint aPosition, double aDeltaX,
double aDeltaY, Modifiers aMods,
bool aIsInertia) {
if (!aWindow) {
return;
}
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");
}
}
TimeStamp eventTimeStamp = TimeStamp::Now();
PanGestureInput event{panGestureType, eventTimeStamp, aPosition,
ScreenPoint(aDeltaX, aDeltaY), aMods};
aWindow->SendAnAPZEvent(event);
}
void DirectManipulationOwner::Init(const LayoutDeviceIntRect& aBounds) {
HRESULT hr = CoCreateInstance(
CLSID_DirectManipulationManager, nullptr, CLSCTX_INPROC_SERVER,
IID_IDirectManipulationManager, getter_AddRefs(mDmManager));
if (!SUCCEEDED(hr)) {
NS_WARNING("CoCreateInstance(CLSID_DirectManipulationManager failed");
mDmManager = nullptr;
return;
}
hr = mDmManager->GetUpdateManager(IID_IDirectManipulationUpdateManager,
getter_AddRefs(mDmUpdateManager));
if (!SUCCEEDED(hr)) {
NS_WARNING("GetUpdateManager failed");
mDmManager = nullptr;
mDmUpdateManager = nullptr;
return;
}
HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW));
hr = mDmManager->CreateViewport(nullptr, wnd, IID_IDirectManipulationViewport,
getter_AddRefs(mDmViewport));
if (!SUCCEEDED(hr)) {
NS_WARNING("CreateViewport failed");
mDmManager = nullptr;
mDmUpdateManager = nullptr;
mDmViewport = nullptr;
return;
}
DIRECTMANIPULATION_CONFIGURATION configuration =
DIRECTMANIPULATION_CONFIGURATION_INTERACTION |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA |
DIRECTMANIPULATION_CONFIGURATION_RAILS_X |
DIRECTMANIPULATION_CONFIGURATION_RAILS_Y;
if (StaticPrefs::apz_allow_zooming()) {
configuration |= DIRECTMANIPULATION_CONFIGURATION_SCALING;
}
hr = mDmViewport->ActivateConfiguration(configuration);
if (!SUCCEEDED(hr)) {
NS_WARNING("ActivateConfiguration failed");
mDmManager = nullptr;
mDmUpdateManager = nullptr;
mDmViewport = nullptr;
return;
}
hr = mDmViewport->SetViewportOptions(
DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE);
if (!SUCCEEDED(hr)) {
NS_WARNING("SetViewportOptions failed");
mDmManager = nullptr;
mDmUpdateManager = nullptr;
mDmViewport = nullptr;
return;
}
mDmHandler = new DManipEventHandler(mWindow, this, aBounds);
hr = mDmViewport->AddEventHandler(wnd, mDmHandler.get(),
&mDmViewportHandlerCookie);
if (!SUCCEEDED(hr)) {
NS_WARNING("AddEventHandler failed");
mDmManager = nullptr;
mDmUpdateManager = nullptr;
mDmViewport = nullptr;
mDmHandler = nullptr;
return;
}
RECT rect = {0, 0, aBounds.Width(), aBounds.Height()};
hr = mDmViewport->SetViewportRect(&rect);
if (!SUCCEEDED(hr)) {
NS_WARNING("SetViewportRect failed");
mDmManager = nullptr;
mDmUpdateManager = nullptr;
mDmViewport = nullptr;
mDmHandler = nullptr;
return;
}
hr = mDmManager->Activate(wnd);
if (!SUCCEEDED(hr)) {
NS_WARNING("manager Activate failed");
mDmManager = nullptr;
mDmUpdateManager = nullptr;
mDmViewport = nullptr;
mDmHandler = nullptr;
return;
}
hr = mDmViewport->Enable();
if (!SUCCEEDED(hr)) {
NS_WARNING("mDmViewport->Enable failed");
mDmManager = nullptr;
mDmUpdateManager = nullptr;
mDmViewport = nullptr;
mDmHandler = nullptr;
return;
}
hr = mDmUpdateManager->Update(nullptr);
if (!SUCCEEDED(hr)) {
NS_WARNING("mDmUpdateManager->Update failed");
mDmManager = nullptr;
mDmUpdateManager = nullptr;
mDmViewport = nullptr;
mDmHandler = nullptr;
return;
}
}
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);
if (!SUCCEEDED(hr)) {
NS_WARNING("SetViewportRect failed");
}
}
}
void DirectManipulationOwner::Destroy() {
if (mDmHandler) {
mDmHandler->mWindow = nullptr;
mDmHandler->mOwner = nullptr;
if (mDmHandler->mObserver) {
gfxWindowsPlatform::GetPlatform()
->GetGlobalVsyncDispatcher()
->RemoveMainThreadObserver(mDmHandler->mObserver);
mDmHandler->mObserver->ClearOwner();
mDmHandler->mObserver = nullptr;
}
}
HRESULT hr;
if (mDmViewport) {
hr = mDmViewport->Stop();
if (!SUCCEEDED(hr)) {
NS_WARNING("mDmViewport->Stop() failed");
}
hr = mDmViewport->Disable();
if (!SUCCEEDED(hr)) {
NS_WARNING("mDmViewport->Disable() failed");
}
hr = mDmViewport->RemoveEventHandler(mDmViewportHandlerCookie);
if (!SUCCEEDED(hr)) {
NS_WARNING("mDmViewport->RemoveEventHandler() failed");
}
hr = mDmViewport->Abandon();
if (!SUCCEEDED(hr)) {
NS_WARNING("mDmViewport->Abandon() failed");
}
}
if (mWindow) {
HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW));
if (mDmManager) {
hr = mDmManager->Deactivate(wnd);
if (!SUCCEEDED(hr)) {
NS_WARNING("mDmManager->Deactivate() failed");
}
}
}
mDmHandler = nullptr;
mDmViewport = nullptr;
mDmUpdateManager = nullptr;
mDmManager = nullptr;
mWindow = nullptr;
}
void DirectManipulationOwner::SetContact(UINT aContactId) {
if (mDmViewport) {
mDmViewport->SetContact(aContactId);
}
}
/*static */ void DirectManipulationOwner::SynthesizeNativeTouchpadPan(
nsWindow* aWindow, nsIWidget::TouchpadGesturePhase aEventPhase,
LayoutDeviceIntPoint aPoint, double aDeltaX, double aDeltaY,
int32_t aModifierFlags) {
DManipEventHandler::SynthesizeNativeTouchpadPan(
aWindow, aEventPhase, aPoint, aDeltaX, aDeltaY, aModifierFlags);
}
/*static */ void DManipEventHandler::SynthesizeNativeTouchpadPan(
nsWindow* aWindow, nsIWidget::TouchpadGesturePhase aEventPhase,
LayoutDeviceIntPoint aPoint, double aDeltaX, double aDeltaY,
int32_t aModifierFlags) {
ScreenPoint position = {(float)aPoint.x, (float)aPoint.y};
Phase phase = Phase::eStart;
if (aEventPhase == nsIWidget::PHASE_UPDATE) {
phase = Phase::eMiddle;
}
if (aEventPhase == nsIWidget::PHASE_END) {
phase = Phase::eEnd;
}
SendPanCommon(aWindow, phase, position, aDeltaX, aDeltaY, aModifierFlags,
/* aIsInertia = */ false);
}
} // namespace widget
} // namespace mozilla