Skip to content

Commit

Permalink
Scroll inertia cancel for Win32 (flutter#34452)
Browse files Browse the repository at this point in the history
  • Loading branch information
moffatman authored Oct 26, 2022
1 parent 43dae0c commit 7ded18a
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 8 deletions.
40 changes: 32 additions & 8 deletions shell/platform/windows/direct_manipulation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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) ||
Expand All @@ -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)) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
11 changes: 11 additions & 0 deletions shell/platform/windows/direct_manipulation.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
105 changes: 105 additions & 0 deletions shell/platform/windows/direct_manipulation_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
20 changes: 20 additions & 0 deletions shell/platform/windows/flutter_windows_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 6 additions & 0 deletions shell/platform/windows/flutter_windows_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
};
Expand Down
4 changes: 4 additions & 0 deletions shell/platform/windows/window_binding_handler_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 7ded18a

Please sign in to comment.