Scroll inertia cancel for Win32 (flutter/engine#34452)
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
|
||||
#include "flutter/fml/logging.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "flutter/shell/platform/windows/direct_manipulation.h"
|
||||
#include "flutter/shell/platform/windows/window.h"
|
||||
#include "flutter/shell/platform/windows/window_binding_handler_delegate.h"
|
||||
@@ -24,6 +26,10 @@
|
||||
|
||||
namespace flutter {
|
||||
|
||||
int32_t DirectManipulationEventHandler::GetDeviceId() {
|
||||
return (int32_t) reinterpret_cast<int64_t>(this);
|
||||
}
|
||||
|
||||
STDMETHODIMP DirectManipulationEventHandler::QueryInterface(REFIID iid,
|
||||
void** ppv) {
|
||||
if ((iid == IID_IUnknown) ||
|
||||
@@ -43,26 +49,39 @@ HRESULT DirectManipulationEventHandler::OnViewportStatusChanged(
|
||||
IDirectManipulationViewport* viewport,
|
||||
DIRECTMANIPULATION_STATUS current,
|
||||
DIRECTMANIPULATION_STATUS previous) {
|
||||
during_inertia_ = current == DIRECTMANIPULATION_INERTIA;
|
||||
if (during_synthesized_reset_ && previous == DIRECTMANIPULATION_RUNNING) {
|
||||
during_synthesized_reset_ = false;
|
||||
} else if (current == DIRECTMANIPULATION_RUNNING) {
|
||||
if (!during_synthesized_reset_) {
|
||||
// Not a false event.
|
||||
if (owner_->binding_handler_delegate) {
|
||||
owner_->binding_handler_delegate->OnPointerPanZoomStart(
|
||||
(int32_t) reinterpret_cast<int64_t>(this));
|
||||
owner_->binding_handler_delegate->OnPointerPanZoomStart(GetDeviceId());
|
||||
}
|
||||
}
|
||||
} else if (previous == DIRECTMANIPULATION_RUNNING) {
|
||||
}
|
||||
if (previous == DIRECTMANIPULATION_RUNNING) {
|
||||
// Reset deltas to ensure only inertia values will be compared later.
|
||||
last_pan_delta_x_ = 0.0;
|
||||
last_pan_delta_y_ = 0.0;
|
||||
if (owner_->binding_handler_delegate) {
|
||||
owner_->binding_handler_delegate->OnPointerPanZoomEnd(
|
||||
(int32_t) reinterpret_cast<int64_t>(this));
|
||||
owner_->binding_handler_delegate->OnPointerPanZoomEnd(GetDeviceId());
|
||||
}
|
||||
} else if (previous == DIRECTMANIPULATION_INERTIA) {
|
||||
if (owner_->binding_handler_delegate &&
|
||||
(std::max)(std::abs(last_pan_delta_x_), std::abs(last_pan_delta_y_)) >
|
||||
0.01) {
|
||||
owner_->binding_handler_delegate->OnScrollInertiaCancel(GetDeviceId());
|
||||
}
|
||||
// Need to reset the content transform to its original position
|
||||
// so that we are ready for the next gesture.
|
||||
// Use during_synthesized_reset_ flag to prevent sending reset also to the
|
||||
// framework.
|
||||
during_synthesized_reset_ = true;
|
||||
last_pan_x_ = 0.0;
|
||||
last_pan_y_ = 0.0;
|
||||
last_pan_delta_x_ = 0.0;
|
||||
last_pan_delta_y_ = 0.0;
|
||||
RECT rect;
|
||||
HRESULT hr = viewport->GetViewportRect(&rect);
|
||||
if (FAILED(hr)) {
|
||||
@@ -104,9 +123,13 @@ HRESULT DirectManipulationEventHandler::OnContentUpdated(
|
||||
float scale = c - (c - transform[0]);
|
||||
float pan_x = transform[4];
|
||||
float pan_y = transform[5];
|
||||
if (owner_->binding_handler_delegate) {
|
||||
last_pan_delta_x_ = pan_x - last_pan_x_;
|
||||
last_pan_delta_y_ = pan_y - last_pan_y_;
|
||||
last_pan_x_ = pan_x;
|
||||
last_pan_y_ = pan_y;
|
||||
if (owner_->binding_handler_delegate && !during_inertia_) {
|
||||
owner_->binding_handler_delegate->OnPointerPanZoomUpdate(
|
||||
(int32_t) reinterpret_cast<int64_t>(this), pan_x, pan_y, scale, 0);
|
||||
GetDeviceId(), pan_x, pan_y, scale, 0);
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
@@ -144,7 +167,8 @@ int DirectManipulationOwner::Init(unsigned int width, unsigned int height) {
|
||||
DIRECTMANIPULATION_CONFIGURATION_INTERACTION |
|
||||
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X |
|
||||
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y |
|
||||
DIRECTMANIPULATION_CONFIGURATION_SCALING;
|
||||
DIRECTMANIPULATION_CONFIGURATION_SCALING |
|
||||
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA;
|
||||
RETURN_IF_FAILED(viewport_->ActivateConfiguration(configuration));
|
||||
RETURN_IF_FAILED(viewport_->SetViewportOptions(
|
||||
DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE));
|
||||
|
||||
@@ -106,12 +106,23 @@ class DirectManipulationEventHandler
|
||||
DIRECTMANIPULATION_INTERACTION_TYPE interaction) override;
|
||||
|
||||
private:
|
||||
// Unique identifier to associate with all gesture event updates.
|
||||
int32_t GetDeviceId();
|
||||
// Parent object, used to store the target for gesture event updates.
|
||||
DirectManipulationOwner* owner_;
|
||||
// We need to reset some parts of DirectManipulation after each gesture
|
||||
// A flag is needed to ensure that false events created as the reset occurs
|
||||
// are not sent to the flutter framework.
|
||||
bool during_synthesized_reset_ = false;
|
||||
// Store whether current events are from synthetic inertia rather than user
|
||||
// input.
|
||||
bool during_inertia_ = false;
|
||||
// Store the difference between the last pan offsets to determine if inertia
|
||||
// has been cancelled in the middle of an animation.
|
||||
float last_pan_x_ = 0.0;
|
||||
float last_pan_y_ = 0.0;
|
||||
float last_pan_delta_x_ = 0.0;
|
||||
float last_pan_delta_y_ = 0.0;
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@@ -259,5 +259,110 @@ TEST(DirectManipulationTest, TestRounding) {
|
||||
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);
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
|
||||
@@ -264,6 +264,11 @@ void FlutterWindowsView::OnScroll(double x,
|
||||
device_id);
|
||||
}
|
||||
|
||||
void FlutterWindowsView::OnScrollInertiaCancel(int32_t device_id) {
|
||||
PointerLocation point = binding_handler_->GetPrimaryPointerLocation();
|
||||
SendScrollInertiaCancel(device_id, point.x, point.y);
|
||||
}
|
||||
|
||||
void FlutterWindowsView::OnUpdateSemanticsEnabled(bool enabled) {
|
||||
engine_->UpdateSemanticsEnabled(enabled);
|
||||
}
|
||||
@@ -500,6 +505,21 @@ void FlutterWindowsView::SendScroll(double x,
|
||||
SendPointerEventWithData(event, state);
|
||||
}
|
||||
|
||||
void FlutterWindowsView::SendScrollInertiaCancel(int32_t device_id,
|
||||
double x,
|
||||
double y) {
|
||||
auto state =
|
||||
GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id);
|
||||
|
||||
FlutterPointerEvent event = {};
|
||||
event.x = x;
|
||||
event.y = y;
|
||||
event.signal_kind =
|
||||
FlutterPointerSignalKind::kFlutterPointerSignalKindScrollInertiaCancel;
|
||||
SetEventPhaseFromCursorButtonState(&event, state);
|
||||
SendPointerEventWithData(event, state);
|
||||
}
|
||||
|
||||
void FlutterWindowsView::SendPointerEventWithData(
|
||||
const FlutterPointerEvent& event_data,
|
||||
PointerState* state) {
|
||||
|
||||
@@ -180,6 +180,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate,
|
||||
FlutterPointerDeviceKind device_kind,
|
||||
int32_t device_id) override;
|
||||
|
||||
// |WindowBindingHandlerDelegate|
|
||||
void OnScrollInertiaCancel(int32_t device_id) override;
|
||||
|
||||
// |WindowBindingHandlerDelegate|
|
||||
virtual void OnUpdateSemanticsEnabled(bool enabled) override;
|
||||
|
||||
@@ -331,6 +334,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate,
|
||||
FlutterPointerDeviceKind device_kind,
|
||||
int32_t device_id);
|
||||
|
||||
// Reports scroll inertia cancel events to Flutter engine.
|
||||
void SendScrollInertiaCancel(int32_t device_id, double x, double y);
|
||||
|
||||
// Creates a PointerState object unless it already exists.
|
||||
PointerState* GetOrCreatePointerState(FlutterPointerDeviceKind device_kind,
|
||||
int32_t device_id);
|
||||
|
||||
@@ -60,6 +60,7 @@ class MockWindowBindingHandlerDelegate : public WindowBindingHandlerDelegate {
|
||||
int,
|
||||
FlutterPointerDeviceKind,
|
||||
int32_t));
|
||||
MOCK_METHOD1(OnScrollInertiaCancel, void(int32_t));
|
||||
MOCK_METHOD0(OnPlatformBrightnessChanged, void());
|
||||
MOCK_METHOD1(UpdateHighContrastEnabled, void(bool enabled));
|
||||
};
|
||||
|
||||
@@ -122,6 +122,10 @@ class WindowBindingHandlerDelegate {
|
||||
FlutterPointerDeviceKind device_kind,
|
||||
int32_t device_id) = 0;
|
||||
|
||||
// Notifies delegate that scroll inertia should be cancelled.
|
||||
// Typically called by DirectManipulationEventHandler
|
||||
virtual void OnScrollInertiaCancel(int32_t device_id) = 0;
|
||||
|
||||
// Notifies delegate that the Flutter semantics tree should be enabled or
|
||||
// disabled.
|
||||
virtual void OnUpdateSemanticsEnabled(bool enabled) = 0;
|
||||
|
||||
Reference in New Issue
Block a user