blob: 323125141edb19486c69ffadf2cbbd14e5e8da07 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/windows/direct_manipulation.h"
#include "flutter/fml/macros.h"
#include "flutter/shell/platform/windows/testing/mock_window_binding_handler_delegate.h"
#include "gtest/gtest.h"
using testing::_;
namespace flutter {
namespace testing {
class MockIDirectManipulationViewport : public IDirectManipulationViewport {
public:
MockIDirectManipulationViewport() {}
MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, AddRef, ULONG());
MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, Release, ULONG());
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
QueryInterface,
HRESULT(REFIID, void**));
MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, Abandon, HRESULT());
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
ActivateConfiguration,
HRESULT(DIRECTMANIPULATION_CONFIGURATION));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
AddConfiguration,
HRESULT(DIRECTMANIPULATION_CONFIGURATION));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
AddContent,
HRESULT(IDirectManipulationContent*));
MOCK_METHOD3_WITH_CALLTYPE(STDMETHODCALLTYPE,
AddEventHandler,
HRESULT(HWND,
IDirectManipulationViewportEventHandler*,
DWORD*));
MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, Disable, HRESULT());
MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, Enable, HRESULT());
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
GetPrimaryContent,
HRESULT(REFIID, void**));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
GetStatus,
HRESULT(DIRECTMANIPULATION_STATUS*));
MOCK_METHOD3_WITH_CALLTYPE(STDMETHODCALLTYPE,
GetTag,
HRESULT(REFIID, void**, UINT32*));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
GetViewportRect,
HRESULT(RECT*));
MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, ReleaseAllContacts, HRESULT());
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
ReleaseContact,
HRESULT(UINT32));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
RemoveConfiguration,
HRESULT(DIRECTMANIPULATION_CONFIGURATION));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
RemoveContent,
HRESULT(IDirectManipulationContent*));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
RemoveEventHandler,
HRESULT(DWORD));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
SetChaining,
HRESULT(DIRECTMANIPULATION_MOTION_TYPES));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, SetContact, HRESULT(UINT32));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
SetInputMode,
HRESULT(DIRECTMANIPULATION_INPUT_MODE));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
SetManualGesture,
HRESULT(DIRECTMANIPULATION_GESTURE_CONFIGURATION));
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
SetTag,
HRESULT(IUnknown*, UINT32));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
SetUpdateMode,
HRESULT(DIRECTMANIPULATION_INPUT_MODE));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
SetViewportOptions,
HRESULT(DIRECTMANIPULATION_VIEWPORT_OPTIONS));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
SetViewportRect,
HRESULT(const RECT*));
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
SetViewportTransform,
HRESULT(const float*, DWORD));
MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, Stop, HRESULT());
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
SyncDisplayTransform,
HRESULT(const float*, DWORD));
MOCK_METHOD5_WITH_CALLTYPE(
STDMETHODCALLTYPE,
ZoomToRect,
HRESULT(const float, const float, const float, const float, BOOL));
private:
FML_DISALLOW_COPY_AND_ASSIGN(MockIDirectManipulationViewport);
};
class MockIDirectManipulationContent : public IDirectManipulationContent {
public:
MockIDirectManipulationContent() {}
MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, AddRef, ULONG());
MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, Release, ULONG());
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
QueryInterface,
HRESULT(REFIID, void**));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, GetContentRect, HRESULT(RECT*));
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
GetContentTransform,
HRESULT(float*, DWORD));
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
GetOutputTransform,
HRESULT(float*, DWORD));
MOCK_METHOD3_WITH_CALLTYPE(STDMETHODCALLTYPE,
GetTag,
HRESULT(REFIID, void**, UINT32*));
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
GetViewport,
HRESULT(REFIID, void**));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE,
SetContentRect,
HRESULT(const RECT*));
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
SetTag,
HRESULT(IUnknown*, UINT32));
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
SyncContentTransform,
HRESULT(const float*, DWORD));
private:
FML_DISALLOW_COPY_AND_ASSIGN(MockIDirectManipulationContent);
};
TEST(DirectManipulationTest, TestGesture) {
MockIDirectManipulationContent content;
MockWindowBindingHandlerDelegate delegate;
MockIDirectManipulationViewport viewport;
const float scale = 1.5;
const float pan_x = 32.0;
const float pan_y = 16.0;
const int DISPLAY_WIDTH = 800;
const int DISPLAY_HEIGHT = 600;
auto owner = std::make_unique<DirectManipulationOwner>(nullptr);
owner->SetBindingHandlerDelegate(&delegate);
auto handler =
fml::MakeRefCounted<DirectManipulationEventHandler>(owner.get());
int32_t device_id = (int32_t) reinterpret_cast<int64_t>(handler.get());
EXPECT_CALL(viewport, GetPrimaryContent(_, _))
.WillOnce(::testing::Invoke([&content](REFIID in, void** out) {
*out = &content;
return S_OK;
}))
.RetiresOnSaturation();
EXPECT_CALL(content, GetContentTransform(_, 6))
.WillOnce(::testing::Invoke([scale](float* transform, DWORD size) {
transform[0] = 1.0f;
transform[4] = 0.0;
transform[5] = 0.0;
return S_OK;
}))
.RetiresOnSaturation();
EXPECT_CALL(delegate, OnPointerPanZoomStart(device_id));
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_RUNNING,
DIRECTMANIPULATION_READY);
EXPECT_CALL(content, GetContentTransform(_, 6))
.WillOnce(::testing::Invoke(
[scale, pan_x, pan_y](float* transform, DWORD size) {
transform[0] = scale;
transform[4] = pan_x;
transform[5] = pan_y;
return S_OK;
}));
EXPECT_CALL(delegate,
OnPointerPanZoomUpdate(device_id, pan_x, pan_y, scale, 0));
handler->OnContentUpdated((IDirectManipulationViewport*)&viewport,
(IDirectManipulationContent*)&content);
EXPECT_CALL(delegate, OnPointerPanZoomEnd(device_id));
EXPECT_CALL(viewport, GetViewportRect(_))
.WillOnce(::testing::Invoke([DISPLAY_WIDTH, DISPLAY_HEIGHT](RECT* rect) {
rect->left = 0;
rect->top = 0;
rect->right = DISPLAY_WIDTH;
rect->bottom = DISPLAY_HEIGHT;
return S_OK;
}));
EXPECT_CALL(viewport, ZoomToRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, false))
.WillOnce(::testing::Return(S_OK));
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_INERTIA,
DIRECTMANIPULATION_RUNNING);
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_READY,
DIRECTMANIPULATION_INERTIA);
}
// Verify that scale mantissa rounding works as expected
TEST(DirectManipulationTest, TestRounding) {
MockIDirectManipulationContent content;
MockWindowBindingHandlerDelegate delegate;
MockIDirectManipulationViewport viewport;
const float scale = 1.5;
const int DISPLAY_WIDTH = 800;
const int DISPLAY_HEIGHT = 600;
auto owner = std::make_unique<DirectManipulationOwner>(nullptr);
owner->SetBindingHandlerDelegate(&delegate);
auto handler =
fml::MakeRefCounted<DirectManipulationEventHandler>(owner.get());
int32_t device_id = (int32_t) reinterpret_cast<int64_t>(handler.get());
EXPECT_CALL(viewport, GetPrimaryContent(_, _))
.WillOnce(::testing::Invoke([&content](REFIID in, void** out) {
*out = &content;
return S_OK;
}))
.RetiresOnSaturation();
EXPECT_CALL(content, GetContentTransform(_, 6))
.WillOnce(::testing::Invoke([scale](float* transform, DWORD size) {
transform[0] = 1.0f;
transform[4] = 0.0;
transform[5] = 0.0;
return S_OK;
}))
.RetiresOnSaturation();
EXPECT_CALL(delegate, OnPointerPanZoomStart(device_id));
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_RUNNING,
DIRECTMANIPULATION_READY);
EXPECT_CALL(content, GetContentTransform(_, 6))
.WillOnce(::testing::Invoke([scale](float* transform, DWORD size) {
transform[0] = 1.5000001f;
transform[4] = 4.0;
transform[5] = 0.0;
return S_OK;
}))
.RetiresOnSaturation();
EXPECT_CALL(delegate,
OnPointerPanZoomUpdate(device_id, 4.0, 0, 1.5000001f, 0))
.Times(0);
EXPECT_CALL(delegate, OnPointerPanZoomUpdate(device_id, 4.0, 0, 1.5f, 0))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(content, GetContentTransform(_, 6))
.WillOnce(::testing::Invoke([scale](float* transform, DWORD size) {
transform[0] = 1.50000065f;
transform[4] = 2.0;
transform[5] = 0.0;
return S_OK;
}))
.RetiresOnSaturation();
EXPECT_CALL(delegate,
OnPointerPanZoomUpdate(device_id, 2.0, 0, 1.50000065f, 0))
.Times(0);
EXPECT_CALL(delegate,
OnPointerPanZoomUpdate(device_id, 2.0, 0, 1.50000047f, 0))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(delegate, OnPointerPanZoomEnd(device_id));
EXPECT_CALL(viewport, GetViewportRect(_))
.WillOnce(::testing::Invoke([DISPLAY_WIDTH, DISPLAY_HEIGHT](RECT* rect) {
rect->left = 0;
rect->top = 0;
rect->right = DISPLAY_WIDTH;
rect->bottom = DISPLAY_HEIGHT;
return S_OK;
}));
EXPECT_CALL(viewport, ZoomToRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, false))
.WillOnce(::testing::Return(S_OK));
handler->OnContentUpdated((IDirectManipulationViewport*)&viewport,
(IDirectManipulationContent*)&content);
handler->OnContentUpdated((IDirectManipulationViewport*)&viewport,
(IDirectManipulationContent*)&content);
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_INERTIA,
DIRECTMANIPULATION_RUNNING);
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_READY,
DIRECTMANIPULATION_INERTIA);
}
TEST(DirectManipulationTest, TestInertiaCancelSentForUserCancel) {
MockIDirectManipulationContent content;
MockWindowBindingHandlerDelegate delegate;
MockIDirectManipulationViewport viewport;
const int DISPLAY_WIDTH = 800;
const int DISPLAY_HEIGHT = 600;
auto owner = std::make_unique<DirectManipulationOwner>(nullptr);
owner->SetBindingHandlerDelegate(&delegate);
auto handler =
fml::MakeRefCounted<DirectManipulationEventHandler>(owner.get());
int32_t device_id = (int32_t) reinterpret_cast<int64_t>(handler.get());
// No need to mock the actual gesture, just start at the end.
EXPECT_CALL(viewport, GetViewportRect(_))
.WillOnce(::testing::Invoke([DISPLAY_WIDTH, DISPLAY_HEIGHT](RECT* rect) {
rect->left = 0;
rect->top = 0;
rect->right = DISPLAY_WIDTH;
rect->bottom = DISPLAY_HEIGHT;
return S_OK;
}));
EXPECT_CALL(viewport, ZoomToRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, false))
.WillOnce(::testing::Return(S_OK));
EXPECT_CALL(delegate, OnPointerPanZoomEnd(device_id));
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_INERTIA,
DIRECTMANIPULATION_RUNNING);
// Have pan_y change by 10 between inertia updates.
EXPECT_CALL(content, GetContentTransform(_, 6))
.WillOnce(::testing::Invoke([](float* transform, DWORD size) {
transform[0] = 1;
transform[4] = 0;
transform[5] = 100;
return S_OK;
}));
handler->OnContentUpdated((IDirectManipulationViewport*)&viewport,
(IDirectManipulationContent*)&content);
EXPECT_CALL(content, GetContentTransform(_, 6))
.WillOnce(::testing::Invoke([](float* transform, DWORD size) {
transform[0] = 1;
transform[4] = 0;
transform[5] = 110;
return S_OK;
}));
handler->OnContentUpdated((IDirectManipulationViewport*)&viewport,
(IDirectManipulationContent*)&content);
// This looks like an interruption in the middle of synthetic inertia because
// of user input.
EXPECT_CALL(delegate, OnScrollInertiaCancel(device_id));
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_READY,
DIRECTMANIPULATION_INERTIA);
}
TEST(DirectManipulationTest, TestInertiaCamcelNotSentAtInertiaEnd) {
MockIDirectManipulationContent content;
MockWindowBindingHandlerDelegate delegate;
MockIDirectManipulationViewport viewport;
const int DISPLAY_WIDTH = 800;
const int DISPLAY_HEIGHT = 600;
auto owner = std::make_unique<DirectManipulationOwner>(nullptr);
owner->SetBindingHandlerDelegate(&delegate);
auto handler =
fml::MakeRefCounted<DirectManipulationEventHandler>(owner.get());
int32_t device_id = (int32_t) reinterpret_cast<int64_t>(handler.get());
// No need to mock the actual gesture, just start at the end.
EXPECT_CALL(viewport, GetViewportRect(_))
.WillOnce(::testing::Invoke([DISPLAY_WIDTH, DISPLAY_HEIGHT](RECT* rect) {
rect->left = 0;
rect->top = 0;
rect->right = DISPLAY_WIDTH;
rect->bottom = DISPLAY_HEIGHT;
return S_OK;
}));
EXPECT_CALL(viewport, ZoomToRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, false))
.WillOnce(::testing::Return(S_OK));
EXPECT_CALL(delegate, OnPointerPanZoomEnd(device_id));
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_INERTIA,
DIRECTMANIPULATION_RUNNING);
// Have no change in pan between events.
EXPECT_CALL(content, GetContentTransform(_, 6))
.WillOnce(::testing::Invoke([](float* transform, DWORD size) {
transform[0] = 1;
transform[4] = 0;
transform[5] = 140;
return S_OK;
}));
handler->OnContentUpdated((IDirectManipulationViewport*)&viewport,
(IDirectManipulationContent*)&content);
EXPECT_CALL(content, GetContentTransform(_, 6))
.WillOnce(::testing::Invoke([](float* transform, DWORD size) {
transform[0] = 1;
transform[4] = 0;
transform[5] = 140;
return S_OK;
}));
handler->OnContentUpdated((IDirectManipulationViewport*)&viewport,
(IDirectManipulationContent*)&content);
// OnScrollInertiaCancel should not be called.
EXPECT_CALL(delegate, OnScrollInertiaCancel(device_id)).Times(0);
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_READY,
DIRECTMANIPULATION_INERTIA);
}
// Have some initial values in the matrix, only the differences should be
// reported.
TEST(DirectManipulationTest, TestGestureWithInitialData) {
MockIDirectManipulationContent content;
MockWindowBindingHandlerDelegate delegate;
MockIDirectManipulationViewport viewport;
const float scale = 1.5;
const float pan_x = 32.0;
const float pan_y = 16.0;
const int DISPLAY_WIDTH = 800;
const int DISPLAY_HEIGHT = 600;
auto owner = std::make_unique<DirectManipulationOwner>(nullptr);
owner->SetBindingHandlerDelegate(&delegate);
auto handler =
fml::MakeRefCounted<DirectManipulationEventHandler>(owner.get());
int32_t device_id = (int32_t) reinterpret_cast<int64_t>(handler.get());
EXPECT_CALL(viewport, GetPrimaryContent(_, _))
.WillOnce(::testing::Invoke([&content](REFIID in, void** out) {
*out = &content;
return S_OK;
}))
.RetiresOnSaturation();
EXPECT_CALL(content, GetContentTransform(_, 6))
.WillOnce(::testing::Invoke([scale](float* transform, DWORD size) {
transform[0] = 2.0f;
transform[4] = 234.0;
transform[5] = 345.0;
return S_OK;
}))
.RetiresOnSaturation();
EXPECT_CALL(delegate, OnPointerPanZoomStart(device_id));
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_RUNNING,
DIRECTMANIPULATION_READY);
EXPECT_CALL(content, GetContentTransform(_, 6))
.WillOnce(::testing::Invoke(
[scale, pan_x, pan_y](float* transform, DWORD size) {
transform[0] = 2.0f * scale;
transform[4] = 234.0 + pan_x;
transform[5] = 345.0 + pan_y;
return S_OK;
}));
EXPECT_CALL(delegate,
OnPointerPanZoomUpdate(device_id, pan_x, pan_y, scale, 0));
handler->OnContentUpdated((IDirectManipulationViewport*)&viewport,
(IDirectManipulationContent*)&content);
EXPECT_CALL(delegate, OnPointerPanZoomEnd(device_id));
EXPECT_CALL(viewport, GetViewportRect(_))
.WillOnce(::testing::Invoke([DISPLAY_WIDTH, DISPLAY_HEIGHT](RECT* rect) {
rect->left = 0;
rect->top = 0;
rect->right = DISPLAY_WIDTH;
rect->bottom = DISPLAY_HEIGHT;
return S_OK;
}));
EXPECT_CALL(viewport, ZoomToRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, false))
.WillOnce(::testing::Return(S_OK));
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_INERTIA,
DIRECTMANIPULATION_RUNNING);
handler->OnViewportStatusChanged((IDirectManipulationViewport*)&viewport,
DIRECTMANIPULATION_READY,
DIRECTMANIPULATION_INERTIA);
}
} // namespace testing
} // namespace flutter