1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "DirectManipulationOwner.h"
8 #include "WinModifierKeyState.h"
10 #include "mozilla/StaticPrefs_apz.h"
11 #include "mozilla/SwipeTracker.h"
12 #include "mozilla/TimeStamp.h"
13 #include "mozilla/VsyncDispatcher.h"
15 #include "directmanipulation.h"
20 class DManipEventHandler
: public IDirectManipulationViewportEventHandler
,
21 public IDirectManipulationInteractionEventHandler
{
23 typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect
;
25 friend class DirectManipulationOwner
;
27 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler
)
29 STDMETHODIMP
QueryInterface(REFIID
, void**) override
;
31 friend class DirectManipulationOwner
;
33 explicit DManipEventHandler(nsWindow
* aWindow
,
34 DirectManipulationOwner
* aOwner
,
35 const LayoutDeviceIntRect
& aBounds
);
37 HRESULT STDMETHODCALLTYPE
OnViewportStatusChanged(
38 IDirectManipulationViewport
* viewport
, DIRECTMANIPULATION_STATUS current
,
39 DIRECTMANIPULATION_STATUS previous
) override
;
41 HRESULT STDMETHODCALLTYPE
42 OnViewportUpdated(IDirectManipulationViewport
* viewport
) override
;
44 HRESULT STDMETHODCALLTYPE
45 OnContentUpdated(IDirectManipulationViewport
* viewport
,
46 IDirectManipulationContent
* content
) override
;
48 HRESULT STDMETHODCALLTYPE
49 OnInteraction(IDirectManipulationViewport2
* viewport
,
50 DIRECTMANIPULATION_INTERACTION_TYPE interaction
) override
;
54 class VObserver final
: public mozilla::VsyncObserver
{
55 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler::VObserver
,
59 void NotifyVsync(const mozilla::VsyncEvent
& aVsync
) override
{
64 explicit VObserver(DManipEventHandler
* aOwner
) : mOwner(aOwner
) {}
66 void ClearOwner() { mOwner
= nullptr; }
69 virtual ~VObserver() {}
70 DManipEventHandler
* mOwner
;
73 enum class State
{ eNone
, ePanning
, eInertia
, ePinching
};
74 void TransitionToState(State aNewState
);
76 enum class Phase
{ eStart
, eMiddle
, eEnd
};
77 // Return value indicates if we sent an event or not and hence if we should
78 // update mLastScale. (We only want to send pinch events if the computed
79 // deltaY for the corresponding WidgetWheelEvent would be non-zero.)
80 bool SendPinch(Phase aPhase
, float aScale
);
81 void SendPan(Phase aPhase
, float x
, float y
, bool aIsInertia
);
82 static void SendPanCommon(nsWindow
* aWindow
, Phase aPhase
,
83 ScreenPoint aPosition
, double aDeltaX
,
84 double aDeltaY
, Modifiers aMods
, bool aIsInertia
);
86 static void SynthesizeNativeTouchpadPan(
87 nsWindow
* aWindow
, nsIWidget::TouchpadGesturePhase aEventPhase
,
88 LayoutDeviceIntPoint aPoint
, double aDeltaX
, double aDeltaY
,
89 int32_t aModifierFlags
);
92 virtual ~DManipEventHandler() = default;
95 DirectManipulationOwner
* mOwner
;
96 RefPtr
<VObserver
> mObserver
;
100 LayoutDeviceIntRect mBounds
;
101 bool mShouldSendPanStart
;
102 bool mShouldSendPinchStart
;
103 State mState
= State::eNone
;
106 DManipEventHandler::DManipEventHandler(nsWindow
* aWindow
,
107 DirectManipulationOwner
* aOwner
,
108 const LayoutDeviceIntRect
& aBounds
)
115 mShouldSendPanStart(false),
116 mShouldSendPinchStart(false) {}
119 DManipEventHandler::QueryInterface(REFIID iid
, void** ppv
) {
120 const IID IID_IDirectManipulationViewportEventHandler
=
121 __uuidof(IDirectManipulationViewportEventHandler
);
122 const IID IID_IDirectManipulationInteractionEventHandler
=
123 __uuidof(IDirectManipulationInteractionEventHandler
);
125 if ((IID_IUnknown
== iid
) ||
126 (IID_IDirectManipulationViewportEventHandler
== iid
)) {
127 *ppv
= static_cast<IDirectManipulationViewportEventHandler
*>(this);
131 if (IID_IDirectManipulationInteractionEventHandler
== iid
) {
132 *ppv
= static_cast<IDirectManipulationInteractionEventHandler
*>(this);
137 return E_NOINTERFACE
;
141 DManipEventHandler::OnViewportStatusChanged(
142 IDirectManipulationViewport
* viewport
, DIRECTMANIPULATION_STATUS current
,
143 DIRECTMANIPULATION_STATUS previous
) {
144 if (current
== previous
) {
148 if (current
== DIRECTMANIPULATION_INERTIA
) {
149 if (previous
!= DIRECTMANIPULATION_RUNNING
|| mState
!= State::ePanning
) {
150 // xxx transition to none?
154 TransitionToState(State::eInertia
);
157 if (current
== DIRECTMANIPULATION_RUNNING
) {
158 // INERTIA -> RUNNING, should start a new sequence.
159 if (previous
== DIRECTMANIPULATION_INERTIA
) {
160 TransitionToState(State::eNone
);
164 if (current
!= DIRECTMANIPULATION_ENABLED
&&
165 current
!= DIRECTMANIPULATION_READY
) {
169 // A session has ended, reset the transform.
170 if (mLastScale
!= 1.f
|| mLastXOffset
!= 0.f
|| mLastYOffset
!= 0.f
) {
172 viewport
->ZoomToRect(0, 0, mBounds
.width
, mBounds
.height
, false);
173 if (!SUCCEEDED(hr
)) {
174 NS_WARNING("ZoomToRect failed");
181 TransitionToState(State::eNone
);
187 DManipEventHandler::OnViewportUpdated(IDirectManipulationViewport
* viewport
) {
191 void DManipEventHandler::TransitionToState(State aNewState
) {
192 if (mState
== aNewState
) {
196 State prevState
= mState
;
199 // End the previous sequence.
201 case State::ePanning
: {
202 // ePanning -> *: PanEnd
203 SendPan(Phase::eEnd
, 0.f
, 0.f
, false);
206 case State::eInertia
: {
207 // eInertia -> *: MomentumEnd
208 SendPan(Phase::eEnd
, 0.f
, 0.f
, true);
211 case State::ePinching
: {
212 MOZ_ASSERT(aNewState
== State::eNone
);
213 // ePinching -> eNone: PinchEnd. ePinching should only transition to
215 // Only send a pinch end if we sent a pinch start.
216 if (!mShouldSendPinchStart
) {
217 SendPinch(Phase::eEnd
, 0.f
);
219 mShouldSendPinchStart
= false;
223 // eNone -> *: no cleanup is needed.
230 // Start the new sequence.
232 case State::ePanning
: {
233 // eInertia, eNone -> ePanning: PanStart.
234 // We're being called from OnContentUpdated, it has the coords we need to
235 // pass to SendPan(Phase::eStart), so set mShouldSendPanStart and when we
236 // return OnContentUpdated will check it and call SendPan(Phase::eStart).
237 mShouldSendPanStart
= true;
240 case State::eInertia
: {
241 // Only ePanning can transition to eInertia.
242 MOZ_ASSERT(prevState
== State::ePanning
);
243 SendPan(Phase::eStart
, 0.f
, 0.f
, true);
246 case State::ePinching
: {
247 // * -> ePinching: PinchStart.
248 // Pinch gesture may begin with some scroll events.
249 // We're being called from OnContentUpdated, it has the scale we need to
250 // pass to SendPinch(Phase::eStart), so set mShouldSendPinchStart and when
251 // we return OnContentUpdated will check it and call
252 // SendPinch(Phase::eStart).
253 mShouldSendPinchStart
= true;
257 // * -> eNone: only cleanup is needed.
266 DManipEventHandler::OnContentUpdated(IDirectManipulationViewport
* viewport
,
267 IDirectManipulationContent
* content
) {
269 HRESULT hr
= content
->GetContentTransform(transform
, ARRAYSIZE(transform
));
270 if (!SUCCEEDED(hr
)) {
271 NS_WARNING("GetContentTransform failed");
275 float scale
= transform
[0];
276 float xoffset
= transform
[4];
277 float yoffset
= transform
[5];
279 // Not different from last time.
280 if (FuzzyEqualsMultiplicative(scale
, mLastScale
) && xoffset
== mLastXOffset
&&
281 yoffset
== mLastYOffset
) {
285 // Consider this is a Scroll when scale factor equals 1.0.
286 if (FuzzyEqualsMultiplicative(scale
, 1.f
)) {
287 if (mState
== State::eNone
) {
288 TransitionToState(State::ePanning
);
291 // Pinch gesture may begin with some scroll events.
292 TransitionToState(State::ePinching
);
295 if (mState
== State::ePanning
|| mState
== State::eInertia
) {
296 // Accumulate the offset (by not updating mLastX/YOffset) until we have at
298 float dx
= std::abs(mLastXOffset
- xoffset
);
299 float dy
= std::abs(mLastYOffset
- yoffset
);
300 if (dx
< 1.f
&& dy
< 1.f
) {
305 bool updateLastScale
= true;
306 if (mState
== State::ePanning
) {
307 if (mShouldSendPanStart
) {
308 SendPan(Phase::eStart
, mLastXOffset
- xoffset
, mLastYOffset
- yoffset
,
310 mShouldSendPanStart
= false;
312 SendPan(Phase::eMiddle
, mLastXOffset
- xoffset
, mLastYOffset
- yoffset
,
315 } else if (mState
== State::eInertia
) {
316 SendPan(Phase::eMiddle
, mLastXOffset
- xoffset
, mLastYOffset
- yoffset
,
318 } else if (mState
== State::ePinching
) {
319 if (mShouldSendPinchStart
) {
320 updateLastScale
= SendPinch(Phase::eStart
, scale
);
321 // Only clear mShouldSendPinchStart if we actually sent the event
322 // (updateLastScale tells us if we sent an event).
323 if (updateLastScale
) {
324 mShouldSendPinchStart
= false;
327 updateLastScale
= SendPinch(Phase::eMiddle
, scale
);
331 if (updateLastScale
) {
334 mLastXOffset
= xoffset
;
335 mLastYOffset
= yoffset
;
341 DManipEventHandler::OnInteraction(
342 IDirectManipulationViewport2
* viewport
,
343 DIRECTMANIPULATION_INTERACTION_TYPE interaction
) {
344 if (interaction
== DIRECTMANIPULATION_INTERACTION_BEGIN
) {
346 mObserver
= new VObserver(this);
349 gfxWindowsPlatform::GetPlatform()
350 ->GetGlobalVsyncDispatcher()
351 ->AddMainThreadObserver(mObserver
);
354 if (mObserver
&& interaction
== DIRECTMANIPULATION_INTERACTION_END
) {
355 gfxWindowsPlatform::GetPlatform()
356 ->GetGlobalVsyncDispatcher()
357 ->RemoveMainThreadObserver(mObserver
);
363 void DManipEventHandler::Update() {
369 void DirectManipulationOwner::Update() {
370 if (mDmUpdateManager
) {
371 mDmUpdateManager
->Update(nullptr);
375 DirectManipulationOwner::DirectManipulationOwner(nsWindow
* aWindow
)
376 : mWindow(aWindow
) {}
378 DirectManipulationOwner::~DirectManipulationOwner() { Destroy(); }
380 bool DManipEventHandler::SendPinch(Phase aPhase
, float aScale
) {
385 if (aScale
== mLastScale
&& aPhase
!= Phase::eEnd
) {
389 PinchGestureInput::PinchGestureType pinchGestureType
=
390 PinchGestureInput::PINCHGESTURE_SCALE
;
393 pinchGestureType
= PinchGestureInput::PINCHGESTURE_START
;
396 pinchGestureType
= PinchGestureInput::PINCHGESTURE_SCALE
;
399 pinchGestureType
= PinchGestureInput::PINCHGESTURE_END
;
402 MOZ_ASSERT_UNREACHABLE("handle all enum values");
405 TimeStamp eventTimeStamp
= TimeStamp::Now();
407 ModifierKeyState modifierKeyState
;
408 Modifiers mods
= modifierKeyState
.GetModifiers();
410 ExternalPoint screenOffset
= ViewAs
<ExternalPixel
>(
411 mWindow
->WidgetToScreenOffset(),
412 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent
);
415 ::GetCursorPos(&cursor_pos
);
416 HWND wnd
= static_cast<HWND
>(mWindow
->GetNativeData(NS_NATIVE_WINDOW
));
417 ::ScreenToClient(wnd
, &cursor_pos
);
418 ScreenPoint position
= {(float)cursor_pos
.x
, (float)cursor_pos
.y
};
420 PinchGestureInput event
{pinchGestureType
,
421 PinchGestureInput::TRACKPAD
,
425 100.0 * ((aPhase
== Phase::eEnd
) ? 1.f
: aScale
),
426 100.0 * ((aPhase
== Phase::eEnd
) ? 1.f
: mLastScale
),
429 if (!event
.SetLineOrPageDeltaY(mWindow
)) {
433 mWindow
->SendAnAPZEvent(event
);
438 void DManipEventHandler::SendPan(Phase aPhase
, float x
, float y
,
444 ModifierKeyState modifierKeyState
;
445 Modifiers mods
= modifierKeyState
.GetModifiers();
448 ::GetCursorPos(&cursor_pos
);
449 HWND wnd
= static_cast<HWND
>(mWindow
->GetNativeData(NS_NATIVE_WINDOW
));
450 ::ScreenToClient(wnd
, &cursor_pos
);
451 ScreenPoint position
= {(float)cursor_pos
.x
, (float)cursor_pos
.y
};
453 SendPanCommon(mWindow
, aPhase
, position
, x
, y
, mods
, aIsInertia
);
457 void DManipEventHandler::SendPanCommon(nsWindow
* aWindow
, Phase aPhase
,
458 ScreenPoint aPosition
, double aDeltaX
,
459 double aDeltaY
, Modifiers aMods
,
465 PanGestureInput::PanGestureType panGestureType
=
466 PanGestureInput::PANGESTURE_PAN
;
470 panGestureType
= PanGestureInput::PANGESTURE_MOMENTUMSTART
;
473 panGestureType
= PanGestureInput::PANGESTURE_MOMENTUMPAN
;
476 panGestureType
= PanGestureInput::PANGESTURE_MOMENTUMEND
;
479 MOZ_ASSERT_UNREACHABLE("handle all enum values");
484 panGestureType
= PanGestureInput::PANGESTURE_START
;
487 panGestureType
= PanGestureInput::PANGESTURE_PAN
;
490 panGestureType
= PanGestureInput::PANGESTURE_END
;
493 MOZ_ASSERT_UNREACHABLE("handle all enum values");
497 TimeStamp eventTimeStamp
= TimeStamp::Now();
499 PanGestureInput event
{panGestureType
, eventTimeStamp
, aPosition
,
500 ScreenPoint(aDeltaX
, aDeltaY
), aMods
};
502 aWindow
->SendAnAPZEvent(event
);
505 void DirectManipulationOwner::Init(const LayoutDeviceIntRect
& aBounds
) {
506 HRESULT hr
= CoCreateInstance(
507 CLSID_DirectManipulationManager
, nullptr, CLSCTX_INPROC_SERVER
,
508 IID_IDirectManipulationManager
, getter_AddRefs(mDmManager
));
509 if (!SUCCEEDED(hr
)) {
510 NS_WARNING("CoCreateInstance(CLSID_DirectManipulationManager failed");
511 mDmManager
= nullptr;
515 hr
= mDmManager
->GetUpdateManager(IID_IDirectManipulationUpdateManager
,
516 getter_AddRefs(mDmUpdateManager
));
517 if (!SUCCEEDED(hr
)) {
518 NS_WARNING("GetUpdateManager failed");
519 mDmManager
= nullptr;
520 mDmUpdateManager
= nullptr;
524 HWND wnd
= static_cast<HWND
>(mWindow
->GetNativeData(NS_NATIVE_WINDOW
));
526 hr
= mDmManager
->CreateViewport(nullptr, wnd
, IID_IDirectManipulationViewport
,
527 getter_AddRefs(mDmViewport
));
528 if (!SUCCEEDED(hr
)) {
529 NS_WARNING("CreateViewport failed");
530 mDmManager
= nullptr;
531 mDmUpdateManager
= nullptr;
532 mDmViewport
= nullptr;
536 DIRECTMANIPULATION_CONFIGURATION configuration
=
537 DIRECTMANIPULATION_CONFIGURATION_INTERACTION
|
538 DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X
|
539 DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y
|
540 DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA
|
541 DIRECTMANIPULATION_CONFIGURATION_RAILS_X
|
542 DIRECTMANIPULATION_CONFIGURATION_RAILS_Y
;
543 if (StaticPrefs::apz_allow_zooming()) {
544 configuration
|= DIRECTMANIPULATION_CONFIGURATION_SCALING
;
547 hr
= mDmViewport
->ActivateConfiguration(configuration
);
548 if (!SUCCEEDED(hr
)) {
549 NS_WARNING("ActivateConfiguration failed");
550 mDmManager
= nullptr;
551 mDmUpdateManager
= nullptr;
552 mDmViewport
= nullptr;
556 hr
= mDmViewport
->SetViewportOptions(
557 DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE
);
558 if (!SUCCEEDED(hr
)) {
559 NS_WARNING("SetViewportOptions failed");
560 mDmManager
= nullptr;
561 mDmUpdateManager
= nullptr;
562 mDmViewport
= nullptr;
566 mDmHandler
= new DManipEventHandler(mWindow
, this, aBounds
);
568 hr
= mDmViewport
->AddEventHandler(wnd
, mDmHandler
.get(),
569 &mDmViewportHandlerCookie
);
570 if (!SUCCEEDED(hr
)) {
571 NS_WARNING("AddEventHandler failed");
572 mDmManager
= nullptr;
573 mDmUpdateManager
= nullptr;
574 mDmViewport
= nullptr;
575 mDmHandler
= nullptr;
579 RECT rect
= {0, 0, aBounds
.Width(), aBounds
.Height()};
580 hr
= mDmViewport
->SetViewportRect(&rect
);
581 if (!SUCCEEDED(hr
)) {
582 NS_WARNING("SetViewportRect failed");
583 mDmManager
= nullptr;
584 mDmUpdateManager
= nullptr;
585 mDmViewport
= nullptr;
586 mDmHandler
= nullptr;
590 hr
= mDmManager
->Activate(wnd
);
591 if (!SUCCEEDED(hr
)) {
592 NS_WARNING("manager Activate failed");
593 mDmManager
= nullptr;
594 mDmUpdateManager
= nullptr;
595 mDmViewport
= nullptr;
596 mDmHandler
= nullptr;
600 hr
= mDmViewport
->Enable();
601 if (!SUCCEEDED(hr
)) {
602 NS_WARNING("mDmViewport->Enable failed");
603 mDmManager
= nullptr;
604 mDmUpdateManager
= nullptr;
605 mDmViewport
= nullptr;
606 mDmHandler
= nullptr;
610 hr
= mDmUpdateManager
->Update(nullptr);
611 if (!SUCCEEDED(hr
)) {
612 NS_WARNING("mDmUpdateManager->Update failed");
613 mDmManager
= nullptr;
614 mDmUpdateManager
= nullptr;
615 mDmViewport
= nullptr;
616 mDmHandler
= nullptr;
621 void DirectManipulationOwner::ResizeViewport(
622 const LayoutDeviceIntRect
& aBounds
) {
624 mDmHandler
->mBounds
= aBounds
;
628 RECT rect
= {0, 0, aBounds
.Width(), aBounds
.Height()};
629 HRESULT hr
= mDmViewport
->SetViewportRect(&rect
);
630 if (!SUCCEEDED(hr
)) {
631 NS_WARNING("SetViewportRect failed");
636 void DirectManipulationOwner::Destroy() {
638 mDmHandler
->mWindow
= nullptr;
639 mDmHandler
->mOwner
= nullptr;
640 if (mDmHandler
->mObserver
) {
641 gfxWindowsPlatform::GetPlatform()
642 ->GetGlobalVsyncDispatcher()
643 ->RemoveMainThreadObserver(mDmHandler
->mObserver
);
644 mDmHandler
->mObserver
->ClearOwner();
645 mDmHandler
->mObserver
= nullptr;
651 hr
= mDmViewport
->Stop();
652 if (!SUCCEEDED(hr
)) {
653 NS_WARNING("mDmViewport->Stop() failed");
656 hr
= mDmViewport
->Disable();
657 if (!SUCCEEDED(hr
)) {
658 NS_WARNING("mDmViewport->Disable() failed");
661 hr
= mDmViewport
->RemoveEventHandler(mDmViewportHandlerCookie
);
662 if (!SUCCEEDED(hr
)) {
663 NS_WARNING("mDmViewport->RemoveEventHandler() failed");
666 hr
= mDmViewport
->Abandon();
667 if (!SUCCEEDED(hr
)) {
668 NS_WARNING("mDmViewport->Abandon() failed");
673 HWND wnd
= static_cast<HWND
>(mWindow
->GetNativeData(NS_NATIVE_WINDOW
));
676 hr
= mDmManager
->Deactivate(wnd
);
677 if (!SUCCEEDED(hr
)) {
678 NS_WARNING("mDmManager->Deactivate() failed");
683 mDmHandler
= nullptr;
684 mDmViewport
= nullptr;
685 mDmUpdateManager
= nullptr;
686 mDmManager
= nullptr;
690 void DirectManipulationOwner::SetContact(UINT aContactId
) {
692 mDmViewport
->SetContact(aContactId
);
696 /*static */ void DirectManipulationOwner::SynthesizeNativeTouchpadPan(
697 nsWindow
* aWindow
, nsIWidget::TouchpadGesturePhase aEventPhase
,
698 LayoutDeviceIntPoint aPoint
, double aDeltaX
, double aDeltaY
,
699 int32_t aModifierFlags
) {
700 DManipEventHandler::SynthesizeNativeTouchpadPan(
701 aWindow
, aEventPhase
, aPoint
, aDeltaX
, aDeltaY
, aModifierFlags
);
704 /*static */ void DManipEventHandler::SynthesizeNativeTouchpadPan(
705 nsWindow
* aWindow
, nsIWidget::TouchpadGesturePhase aEventPhase
,
706 LayoutDeviceIntPoint aPoint
, double aDeltaX
, double aDeltaY
,
707 int32_t aModifierFlags
) {
708 ScreenPoint position
= {(float)aPoint
.x
, (float)aPoint
.y
};
709 Phase phase
= Phase::eStart
;
710 if (aEventPhase
== nsIWidget::PHASE_UPDATE
) {
711 phase
= Phase::eMiddle
;
714 if (aEventPhase
== nsIWidget::PHASE_END
) {
717 SendPanCommon(aWindow
, phase
, position
, aDeltaX
, aDeltaY
, aModifierFlags
,
718 /* aIsInertia = */ false);
721 } // namespace widget
722 } // namespace mozilla