From f6977e39d83fbfdc5014a5a6fb9194b23ddb8ca4 Mon Sep 17 00:00:00 2001 From: Niklas Schulze Date: Fri, 13 Aug 2021 06:02:01 +0200 Subject: [PATCH] Windows: Add multi-touch support (flutter/engine#27863) --- .../ci/licenses_golden/licenses_flutter | 3 + .../flutter/shell/platform/windows/BUILD.gn | 3 + .../platform/windows/flutter_window_win32.cc | 54 ++++---- .../platform/windows/flutter_window_win32.h | 25 +++- .../windows/flutter_window_win32_unittests.cc | 118 ++++++++++------ .../platform/windows/flutter_window_winuwp.cc | 16 ++- .../platform/windows/flutter_windows_view.cc | 128 +++++++++++------- .../platform/windows/flutter_windows_view.h | 81 ++++++----- .../windows/game_pad_cursor_winuwp.cc | 14 +- .../windows/sequential_id_generator.cc | 77 +++++++++++ .../windows/sequential_id_generator.h | 59 ++++++++ .../sequential_id_generator_unittests.cc | 49 +++++++ .../windows/testing/mock_window_win32.h | 14 +- .../windows/window_binding_handler_delegate.h | 12 +- .../shell/platform/windows/window_win32.cc | 107 +++++++++++++-- .../shell/platform/windows/window_win32.h | 47 +++++-- .../windows/window_win32_unittests.cc | 8 +- 17 files changed, 611 insertions(+), 204 deletions(-) create mode 100644 engine/src/flutter/shell/platform/windows/sequential_id_generator.cc create mode 100644 engine/src/flutter/shell/platform/windows/sequential_id_generator.h create mode 100644 engine/src/flutter/shell/platform/windows/sequential_id_generator_unittests.cc diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 01144e49c7f..a248f67405b 100755 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -1662,6 +1662,9 @@ FILE: ../../../flutter/shell/platform/windows/platform_handler_win32.h FILE: ../../../flutter/shell/platform/windows/platform_handler_winuwp.cc FILE: ../../../flutter/shell/platform/windows/platform_handler_winuwp.h FILE: ../../../flutter/shell/platform/windows/public/flutter_windows.h +FILE: ../../../flutter/shell/platform/windows/sequential_id_generator.cc +FILE: ../../../flutter/shell/platform/windows/sequential_id_generator.h +FILE: ../../../flutter/shell/platform/windows/sequential_id_generator_unittests.cc FILE: ../../../flutter/shell/platform/windows/string_conversion.cc FILE: ../../../flutter/shell/platform/windows/string_conversion.h FILE: ../../../flutter/shell/platform/windows/string_conversion_unittests.cc diff --git a/engine/src/flutter/shell/platform/windows/BUILD.gn b/engine/src/flutter/shell/platform/windows/BUILD.gn index df8a6c11751..7eb2107505a 100644 --- a/engine/src/flutter/shell/platform/windows/BUILD.gn +++ b/engine/src/flutter/shell/platform/windows/BUILD.gn @@ -74,6 +74,8 @@ source_set("flutter_windows_source") { "keyboard_key_handler.h", "platform_handler.cc", "platform_handler.h", + "sequential_id_generator.cc", + "sequential_id_generator.h", "system_utils.h", "task_runner.h", "text_input_plugin.cc", @@ -221,6 +223,7 @@ executable("flutter_windows_unittests") { sources = [ # "flutter_project_bundle_unittests.cc", //TODO failing due to switches test failing. Blocked on https://github.com/flutter/flutter/issues/74153 # "flutter_windows_engine_unittests.cc", //TODO failing to send / receive platform message get plugins working first. Blocked on https://github.com/flutter/flutter/issues/74155 + "sequential_id_generator_unittests.cc", "string_conversion_unittests.cc", "system_utils_unittests.cc", "testing/engine_modifier.h", diff --git a/engine/src/flutter/shell/platform/windows/flutter_window_win32.cc b/engine/src/flutter/shell/platform/windows/flutter_window_win32.cc index a6fd8bd05bd..9983de587e4 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_window_win32.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_window_win32.cc @@ -128,24 +128,6 @@ static uint64_t ConvertWinButtonToFlutterButton(UINT button) { return 0; } -// This method is only valid during a window message related to mouse/touch -// input. -// See -// https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages?redirectedfrom=MSDN#distinguishing-pen-input-from-mouse-and-touch. -static FlutterPointerDeviceKind GetFlutterPointerDeviceKind() { - constexpr LPARAM kTouchOrPenSignature = 0xFF515700; - constexpr LPARAM kTouchSignature = kTouchOrPenSignature | 0x80; - constexpr LPARAM kSignatureMask = 0xFFFFFF00; - LPARAM info = GetMessageExtraInfo(); - if ((info & kSignatureMask) == kTouchOrPenSignature) { - if ((info & kTouchSignature) == kTouchSignature) { - return kFlutterPointerDeviceKindTouch; - } - return kFlutterPointerDeviceKindStylus; - } - return kFlutterPointerDeviceKindMouse; -} - void FlutterWindowWin32::OnDpiScale(unsigned int dpi){}; // When DesktopWindow notifies that a WM_Size message has come in @@ -156,30 +138,42 @@ void FlutterWindowWin32::OnResize(unsigned int width, unsigned int height) { } } -void FlutterWindowWin32::OnPointerMove(double x, double y) { - binding_handler_delegate_->OnPointerMove(x, y, GetFlutterPointerDeviceKind()); +void FlutterWindowWin32::OnPointerMove(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id) { + binding_handler_delegate_->OnPointerMove(x, y, device_kind, device_id); } -void FlutterWindowWin32::OnPointerDown(double x, double y, UINT button) { +void FlutterWindowWin32::OnPointerDown(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id, + UINT button) { uint64_t flutter_button = ConvertWinButtonToFlutterButton(button); if (flutter_button != 0) { binding_handler_delegate_->OnPointerDown( - x, y, GetFlutterPointerDeviceKind(), + x, y, device_kind, device_id, static_cast(flutter_button)); } } -void FlutterWindowWin32::OnPointerUp(double x, double y, UINT button) { +void FlutterWindowWin32::OnPointerUp(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id, + UINT button) { uint64_t flutter_button = ConvertWinButtonToFlutterButton(button); if (flutter_button != 0) { binding_handler_delegate_->OnPointerUp( - x, y, GetFlutterPointerDeviceKind(), + x, y, device_kind, device_id, static_cast(flutter_button)); } } -void FlutterWindowWin32::OnPointerLeave() { - binding_handler_delegate_->OnPointerLeave(GetFlutterPointerDeviceKind()); +void FlutterWindowWin32::OnPointerLeave(FlutterPointerDeviceKind device_kind, + int32_t device_id) { + binding_handler_delegate_->OnPointerLeave(device_kind, device_id); } void FlutterWindowWin32::OnSetCursor() { @@ -217,13 +211,17 @@ void FlutterWindowWin32::OnComposeChange(const std::u16string& text, binding_handler_delegate_->OnComposeChange(text, cursor_pos); } -void FlutterWindowWin32::OnScroll(double delta_x, double delta_y) { +void FlutterWindowWin32::OnScroll(double delta_x, + double delta_y, + FlutterPointerDeviceKind device_kind, + int32_t device_id) { POINT point; GetCursorPos(&point); ScreenToClient(GetWindowHandle(), &point); binding_handler_delegate_->OnScroll(point.x, point.y, delta_x, delta_y, - kScrollOffsetMultiplier); + kScrollOffsetMultiplier, device_kind, + device_id); } void FlutterWindowWin32::OnCursorRectUpdated(const Rect& rect) { diff --git a/engine/src/flutter/shell/platform/windows/flutter_window_win32.h b/engine/src/flutter/shell/platform/windows/flutter_window_win32.h index 4279b7afba0..86969749d21 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_window_win32.h +++ b/engine/src/flutter/shell/platform/windows/flutter_window_win32.h @@ -37,16 +37,28 @@ class FlutterWindowWin32 : public WindowWin32, public WindowBindingHandler { void OnResize(unsigned int width, unsigned int height) override; // |WindowWin32| - void OnPointerMove(double x, double y) override; + void OnPointerMove(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id) override; // |WindowWin32| - void OnPointerDown(double x, double y, UINT button) override; + void OnPointerDown(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id, + UINT button) override; // |WindowWin32| - void OnPointerUp(double x, double y, UINT button) override; + void OnPointerUp(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id, + UINT button) override; // |WindowWin32| - void OnPointerLeave() override; + void OnPointerLeave(FlutterPointerDeviceKind device_kind, + int32_t device_id) override; // |WindowWin32| void OnSetCursor() override; @@ -78,7 +90,10 @@ class FlutterWindowWin32 : public WindowWin32, public WindowBindingHandler { void OnCursorRectUpdated(const Rect& rect) override; // |WindowWin32| - void OnScroll(double delta_x, double delta_y) override; + void OnScroll(double delta_x, + double delta_y, + FlutterPointerDeviceKind device_kind, + int32_t device_id) override; // |FlutterWindowBindingHandler| void SetView(WindowBindingHandlerDelegate* view) override; diff --git a/engine/src/flutter/shell/platform/windows/flutter_window_win32_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_window_win32_unittests.cc index 93763e3e841..0340e2fc554 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_window_win32_unittests.cc @@ -28,6 +28,7 @@ namespace flutter { namespace testing { namespace { +static constexpr int32_t kDefaultPointerDeviceId = 0; // A key event handler that can be spied on while it forwards calls to the real // key event handler. @@ -141,12 +142,16 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32, MOCK_METHOD1(OnDpiScale, void(unsigned int)); MOCK_METHOD2(OnResize, void(unsigned int, unsigned int)); - MOCK_METHOD2(OnPointerMove, void(double, double)); - MOCK_METHOD3(OnPointerDown, void(double, double, UINT)); - MOCK_METHOD3(OnPointerUp, void(double, double, UINT)); - MOCK_METHOD0(OnPointerLeave, void()); + MOCK_METHOD4(OnPointerMove, + void(double, double, FlutterPointerDeviceKind, int32_t)); + MOCK_METHOD5(OnPointerDown, + void(double, double, FlutterPointerDeviceKind, int32_t, UINT)); + MOCK_METHOD5(OnPointerUp, + void(double, double, FlutterPointerDeviceKind, int32_t, UINT)); + MOCK_METHOD2(OnPointerLeave, void(FlutterPointerDeviceKind, int32_t)); MOCK_METHOD0(OnSetCursor, void()); - MOCK_METHOD2(OnScroll, void(double, double)); + MOCK_METHOD4(OnScroll, + void(double, double, FlutterPointerDeviceKind, int32_t)); MOCK_METHOD0(GetDpiScale, float()); MOCK_METHOD0(IsVisible, bool()); MOCK_METHOD1(UpdateCursorRect, void(const Rect&)); @@ -188,25 +193,35 @@ class MockWindowBindingHandlerDelegate : public WindowBindingHandlerDelegate { MockWindowBindingHandlerDelegate const&) = delete; MOCK_METHOD2(OnWindowSizeChanged, void(size_t, size_t)); - MOCK_METHOD3(OnPointerMove, void(double, double, FlutterPointerDeviceKind)); - MOCK_METHOD4(OnPointerDown, + MOCK_METHOD4(OnPointerMove, + void(double, double, FlutterPointerDeviceKind, int32_t)); + MOCK_METHOD5(OnPointerDown, void(double, double, FlutterPointerDeviceKind, + int32_t, FlutterPointerMouseButtons)); - MOCK_METHOD4(OnPointerUp, + MOCK_METHOD5(OnPointerUp, void(double, double, FlutterPointerDeviceKind, + int32_t, FlutterPointerMouseButtons)); - MOCK_METHOD1(OnPointerLeave, void(FlutterPointerDeviceKind)); + MOCK_METHOD2(OnPointerLeave, void(FlutterPointerDeviceKind, int32_t)); MOCK_METHOD1(OnText, void(const std::u16string&)); MOCK_METHOD6(OnKey, bool(int, int, int, char32_t, bool, bool)); MOCK_METHOD0(OnComposeBegin, void()); MOCK_METHOD0(OnComposeCommit, void()); MOCK_METHOD0(OnComposeEnd, void()); MOCK_METHOD2(OnComposeChange, void(const std::u16string&, int)); - MOCK_METHOD5(OnScroll, void(double, double, double, double, int)); + MOCK_METHOD7(OnScroll, + void(double, + double, + double, + double, + int, + FlutterPointerDeviceKind, + int32_t)); }; // A FlutterWindowsView that overrides the RegisterKeyboardHandlers function @@ -517,69 +532,88 @@ TEST(FlutterWindowWin32Test, OnPointerStarSendsDeviceType) { win32window.SetView(&delegate); // Move EXPECT_CALL(delegate, - OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindMouse)) + OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId)) .Times(1); EXPECT_CALL(delegate, - OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindTouch)) + OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindTouch, + kDefaultPointerDeviceId)) .Times(1); EXPECT_CALL(delegate, - OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindStylus)) + OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindStylus, + kDefaultPointerDeviceId)) .Times(1); // Down - EXPECT_CALL(delegate, - OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindMouse, - kFlutterPointerButtonMousePrimary)) + EXPECT_CALL( + delegate, + OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, kFlutterPointerButtonMousePrimary)) .Times(1); - EXPECT_CALL(delegate, - OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindTouch, - kFlutterPointerButtonMousePrimary)) + EXPECT_CALL( + delegate, + OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindTouch, + kDefaultPointerDeviceId, kFlutterPointerButtonMousePrimary)) .Times(1); - EXPECT_CALL(delegate, - OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindStylus, - kFlutterPointerButtonMousePrimary)) + EXPECT_CALL( + delegate, + OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindStylus, + kDefaultPointerDeviceId, kFlutterPointerButtonMousePrimary)) .Times(1); // Up EXPECT_CALL(delegate, OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, kFlutterPointerButtonMousePrimary)) .Times(1); EXPECT_CALL(delegate, OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindTouch, + kDefaultPointerDeviceId, kFlutterPointerButtonMousePrimary)) .Times(1); EXPECT_CALL(delegate, OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindStylus, + kDefaultPointerDeviceId, kFlutterPointerButtonMousePrimary)) .Times(1); // Leave - EXPECT_CALL(delegate, OnPointerLeave(kFlutterPointerDeviceKindMouse)) + EXPECT_CALL(delegate, OnPointerLeave(kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId)) .Times(1); - EXPECT_CALL(delegate, OnPointerLeave(kFlutterPointerDeviceKindTouch)) + EXPECT_CALL(delegate, OnPointerLeave(kFlutterPointerDeviceKindTouch, + kDefaultPointerDeviceId)) .Times(1); - EXPECT_CALL(delegate, OnPointerLeave(kFlutterPointerDeviceKindStylus)) + EXPECT_CALL(delegate, OnPointerLeave(kFlutterPointerDeviceKindStylus, + kDefaultPointerDeviceId)) .Times(1); - win32window.OnPointerMove(10.0, 10.0); - win32window.OnPointerDown(10.0, 10.0, WM_LBUTTONDOWN); - win32window.OnPointerUp(10.0, 10.0, WM_LBUTTONDOWN); - win32window.OnPointerLeave(); + win32window.OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId); + win32window.OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, WM_LBUTTONDOWN); + win32window.OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, WM_LBUTTONDOWN); + win32window.OnPointerLeave(kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId); // Touch - LPARAM original_lparam = SetMessageExtraInfo(0xFF51578b); - win32window.OnPointerMove(10.0, 10.0); - win32window.OnPointerDown(10.0, 10.0, WM_LBUTTONDOWN); - win32window.OnPointerUp(10.0, 10.0, WM_LBUTTONDOWN); - win32window.OnPointerLeave(); + win32window.OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindTouch, + kDefaultPointerDeviceId); + win32window.OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindTouch, + kDefaultPointerDeviceId, WM_LBUTTONDOWN); + win32window.OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindTouch, + kDefaultPointerDeviceId, WM_LBUTTONDOWN); + win32window.OnPointerLeave(kFlutterPointerDeviceKindTouch, + kDefaultPointerDeviceId); // Pen - SetMessageExtraInfo(0xFF515700); - win32window.OnPointerMove(10.0, 10.0); - win32window.OnPointerDown(10.0, 10.0, WM_LBUTTONDOWN); - win32window.OnPointerUp(10.0, 10.0, WM_LBUTTONDOWN); - win32window.OnPointerLeave(); - - // Reset extra info for other tests. - SetMessageExtraInfo(original_lparam); + win32window.OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindStylus, + kDefaultPointerDeviceId); + win32window.OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindStylus, + kDefaultPointerDeviceId, WM_LBUTTONDOWN); + win32window.OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindStylus, + kDefaultPointerDeviceId, WM_LBUTTONDOWN); + win32window.OnPointerLeave(kFlutterPointerDeviceKindStylus, + kDefaultPointerDeviceId); } } // namespace testing diff --git a/engine/src/flutter/shell/platform/windows/flutter_window_winuwp.cc b/engine/src/flutter/shell/platform/windows/flutter_window_winuwp.cc index 34872c4e189..2316e0e9a83 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_window_winuwp.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_window_winuwp.cc @@ -9,6 +9,11 @@ namespace flutter { // Multipler used to map controller velocity to an appropriate scroll input. static constexpr double kControllerScrollMultiplier = 3; +// TODO(clarkezone): Determine pointer ID in +// OnPointerPressed/OnPointerReleased/OnPointerMoved in order to support multi +// touch. See https://github.com/flutter/flutter/issues/70201 +static constexpr int32_t kDefaultPointerDeviceId = 0; + FlutterWindowWinUWP::FlutterWindowWinUWP( ABI::Windows::ApplicationModel::Core::CoreApplicationView* applicationview) { @@ -146,7 +151,7 @@ void FlutterWindowWinUWP::OnPointerPressed( FlutterPointerDeviceKind device_kind = GetPointerDeviceKind(args); binding_handler_delegate_->OnPointerDown( - x, y, device_kind, + x, y, device_kind, kDefaultPointerDeviceId, FlutterPointerMouseButtons::kFlutterPointerButtonMousePrimary); } @@ -158,7 +163,7 @@ void FlutterWindowWinUWP::OnPointerReleased( FlutterPointerDeviceKind device_kind = GetPointerDeviceKind(args); binding_handler_delegate_->OnPointerUp( - x, y, device_kind, + x, y, device_kind, kDefaultPointerDeviceId, FlutterPointerMouseButtons::kFlutterPointerButtonMousePrimary); } @@ -169,7 +174,8 @@ void FlutterWindowWinUWP::OnPointerMoved( double y = GetPosY(args); FlutterPointerDeviceKind device_kind = GetPointerDeviceKind(args); - binding_handler_delegate_->OnPointerMove(x, y, device_kind); + binding_handler_delegate_->OnPointerMove(x, y, device_kind, + kDefaultPointerDeviceId); } void FlutterWindowWinUWP::OnPointerWheelChanged( @@ -177,8 +183,10 @@ void FlutterWindowWinUWP::OnPointerWheelChanged( winrt::Windows::UI::Core::PointerEventArgs const& args) { double x = GetPosX(args); double y = GetPosY(args); + FlutterPointerDeviceKind device_kind = GetPointerDeviceKind(args); int delta = args.CurrentPoint().Properties().MouseWheelDelta(); - binding_handler_delegate_->OnScroll(x, y, 0, -delta, 1); + binding_handler_delegate_->OnScroll(x, y, 0, -delta, 1, device_kind, + kDefaultPointerDeviceId); } double FlutterWindowWinUWP::GetPosX( diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_view.cc b/engine/src/flutter/shell/platform/windows/flutter_windows_view.cc index fee32e5f7da..554938bac41 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_view.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_view.cc @@ -172,19 +172,21 @@ void FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) { void FlutterWindowsView::OnPointerMove(double x, double y, - FlutterPointerDeviceKind device_kind) { - SendPointerMove(x, y, device_kind); + FlutterPointerDeviceKind device_kind, + int32_t device_id) { + SendPointerMove(x, y, GetOrCreatePointerState(device_kind, device_id)); } void FlutterWindowsView::OnPointerDown( double x, double y, FlutterPointerDeviceKind device_kind, + int32_t device_id, FlutterPointerMouseButtons flutter_button) { if (flutter_button != 0) { - uint64_t mouse_buttons = mouse_state_.buttons | flutter_button; - SetMouseButtons(mouse_buttons); - SendPointerDown(x, y, device_kind); + auto state = GetOrCreatePointerState(device_kind, device_id); + state->buttons |= flutter_button; + SendPointerDown(x, y, state); } } @@ -192,16 +194,18 @@ void FlutterWindowsView::OnPointerUp( double x, double y, FlutterPointerDeviceKind device_kind, + int32_t device_id, FlutterPointerMouseButtons flutter_button) { if (flutter_button != 0) { - uint64_t mouse_buttons = mouse_state_.buttons & ~flutter_button; - SetMouseButtons(mouse_buttons); - SendPointerUp(x, y, device_kind); + auto state = GetOrCreatePointerState(device_kind, device_id); + state->buttons &= ~flutter_button; + SendPointerUp(x, y, state); } } -void FlutterWindowsView::OnPointerLeave(FlutterPointerDeviceKind device_kind) { - SendPointerLeave(device_kind); +void FlutterWindowsView::OnPointerLeave(FlutterPointerDeviceKind device_kind, + int32_t device_id) { + SendPointerLeave(GetOrCreatePointerState(device_kind, device_id)); } void FlutterWindowsView::OnText(const std::u16string& text) { @@ -238,8 +242,11 @@ void FlutterWindowsView::OnScroll(double x, double y, double delta_x, double delta_y, - int scroll_offset_multiplier) { - SendScroll(x, y, delta_x, delta_y, scroll_offset_multiplier); + int scroll_offset_multiplier, + FlutterPointerDeviceKind device_kind, + int32_t device_id) { + SendScroll(x, y, delta_x, delta_y, scroll_offset_multiplier, device_kind, + device_id); } void FlutterWindowsView::OnCursorRectUpdated(const Rect& rect) { @@ -265,18 +272,38 @@ void FlutterWindowsView::SendInitialBounds() { binding_handler_->GetDpiScale()); } +FlutterWindowsView::PointerState* FlutterWindowsView::GetOrCreatePointerState( + FlutterPointerDeviceKind device_kind, + int32_t device_id) { + // Create a virtual pointer ID that is unique across all device types + // to prevent pointers from clashing in the engine's converter + // (lib/ui/window/pointer_data_packet_converter.cc) + int32_t pointer_id = (static_cast(device_kind) << 28) | device_id; + + auto [it, added] = pointer_states_.try_emplace(pointer_id, nullptr); + if (added) { + auto state = std::make_unique(); + state->device_kind = device_kind; + state->pointer_id = pointer_id; + it->second = std::move(state); + } + + return it->second.get(); +} + // Set's |event_data|'s phase to either kMove or kHover depending on the current // primary mouse button state. void FlutterWindowsView::SetEventPhaseFromCursorButtonState( - FlutterPointerEvent* event_data) const { + FlutterPointerEvent* event_data, + const PointerState* state) const { // For details about this logic, see FlutterPointerPhase in the embedder.h // file. - if (mouse_state_.buttons == 0) { - event_data->phase = mouse_state_.flutter_state_is_down + if (state->buttons == 0) { + event_data->phase = state->flutter_state_is_down ? FlutterPointerPhase::kUp : FlutterPointerPhase::kHover; } else { - event_data->phase = mouse_state_.flutter_state_is_down + event_data->phase = state->flutter_state_is_down ? FlutterPointerPhase::kMove : FlutterPointerPhase::kDown; } @@ -284,47 +311,46 @@ void FlutterWindowsView::SetEventPhaseFromCursorButtonState( void FlutterWindowsView::SendPointerMove(double x, double y, - FlutterPointerDeviceKind device_kind) { + PointerState* state) { FlutterPointerEvent event = {}; event.x = x; event.y = y; - event.device_kind = device_kind; - SetEventPhaseFromCursorButtonState(&event); - SendPointerEventWithData(event); + + SetEventPhaseFromCursorButtonState(&event, state); + SendPointerEventWithData(event, state); } void FlutterWindowsView::SendPointerDown(double x, double y, - FlutterPointerDeviceKind device_kind) { + PointerState* state) { FlutterPointerEvent event = {}; - SetEventPhaseFromCursorButtonState(&event); event.x = x; event.y = y; - event.device_kind = device_kind; - SendPointerEventWithData(event); - SetMouseFlutterStateDown(true); + + SetEventPhaseFromCursorButtonState(&event, state); + SendPointerEventWithData(event, state); + + state->flutter_state_is_down = true; } void FlutterWindowsView::SendPointerUp(double x, double y, - FlutterPointerDeviceKind device_kind) { + PointerState* state) { FlutterPointerEvent event = {}; - SetEventPhaseFromCursorButtonState(&event); event.x = x; event.y = y; - event.device_kind = device_kind; - SendPointerEventWithData(event); + + SetEventPhaseFromCursorButtonState(&event, state); + SendPointerEventWithData(event, state); if (event.phase == FlutterPointerPhase::kUp) { - SetMouseFlutterStateDown(false); + state->flutter_state_is_down = false; } } -void FlutterWindowsView::SendPointerLeave( - FlutterPointerDeviceKind device_kind) { +void FlutterWindowsView::SendPointerLeave(PointerState* state) { FlutterPointerEvent event = {}; - event.device_kind = device_kind; event.phase = FlutterPointerPhase::kRemove; - SendPointerEventWithData(event); + SendPointerEventWithData(event, state); } void FlutterWindowsView::SendText(const std::u16string& text) { @@ -378,39 +404,47 @@ void FlutterWindowsView::SendScroll(double x, double y, double delta_x, double delta_y, - int scroll_offset_multiplier) { + int scroll_offset_multiplier, + FlutterPointerDeviceKind device_kind, + int32_t device_id) { + auto state = GetOrCreatePointerState(device_kind, device_id); + FlutterPointerEvent event = {}; - SetEventPhaseFromCursorButtonState(&event); - event.signal_kind = FlutterPointerSignalKind::kFlutterPointerSignalKindScroll; event.x = x; event.y = y; + event.signal_kind = FlutterPointerSignalKind::kFlutterPointerSignalKindScroll; event.scroll_delta_x = delta_x * scroll_offset_multiplier; event.scroll_delta_y = delta_y * scroll_offset_multiplier; - SendPointerEventWithData(event); + SetEventPhaseFromCursorButtonState(&event, state); + SendPointerEventWithData(event, state); } void FlutterWindowsView::SendPointerEventWithData( - const FlutterPointerEvent& event_data) { + const FlutterPointerEvent& event_data, + PointerState* state) { // If sending anything other than an add, and the pointer isn't already added, // synthesize an add to satisfy Flutter's expectations about events. - if (!mouse_state_.flutter_state_is_added && + if (!state->flutter_state_is_added && event_data.phase != FlutterPointerPhase::kAdd) { FlutterPointerEvent event = {}; event.phase = FlutterPointerPhase::kAdd; event.x = event_data.x; event.y = event_data.y; event.buttons = 0; - SendPointerEventWithData(event); + SendPointerEventWithData(event, state); } + // Don't double-add (e.g., if events are delivered out of order, so an add has // already been synthesized). - if (mouse_state_.flutter_state_is_added && + if (state->flutter_state_is_added && event_data.phase == FlutterPointerPhase::kAdd) { return; } FlutterPointerEvent event = event_data; - event.buttons = mouse_state_.buttons; + event.device_kind = state->device_kind; + event.device = state->pointer_id; + event.buttons = state->buttons; // Set metadata that's always the same regardless of the event. event.struct_size = sizeof(event); @@ -422,10 +456,12 @@ void FlutterWindowsView::SendPointerEventWithData( engine_->SendPointerEvent(event); if (event_data.phase == FlutterPointerPhase::kAdd) { - SetMouseFlutterStateAdded(true); + state->flutter_state_is_added = true; } else if (event_data.phase == FlutterPointerPhase::kRemove) { - SetMouseFlutterStateAdded(false); - ResetMouseState(); + auto it = pointer_states_.find(state->pointer_id); + if (it != pointer_states_.end()) { + pointer_states_.erase(it); + } } } diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_view.h b/engine/src/flutter/shell/platform/windows/flutter_windows_view.h index 4a48499686b..9f5fd7559fb 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_view.h +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_view.h @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include "flutter/shell/platform/common/client_wrapper/include/flutter/plugin_registrar.h" @@ -94,22 +96,26 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, // |WindowBindingHandlerDelegate| void OnPointerMove(double x, double y, - FlutterPointerDeviceKind device_kind) override; + FlutterPointerDeviceKind device_kind, + int32_t device_id) override; // |WindowBindingHandlerDelegate| void OnPointerDown(double x, double y, FlutterPointerDeviceKind device_kind, + int32_t device_id, FlutterPointerMouseButtons button) override; // |WindowBindingHandlerDelegate| void OnPointerUp(double x, double y, FlutterPointerDeviceKind device_kind, + int32_t device_id, FlutterPointerMouseButtons button) override; // |WindowBindingHandlerDelegate| - void OnPointerLeave(FlutterPointerDeviceKind device_kind) override; + void OnPointerLeave(FlutterPointerDeviceKind device_kind, + int32_t device_id = 0) override; // |WindowBindingHandlerDelegate| void OnText(const std::u16string&) override; @@ -139,7 +145,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, double y, double delta_x, double delta_y, - int scroll_offset_multiplier) override; + int scroll_offset_multiplier, + FlutterPointerDeviceKind device_kind, + int32_t device_id) override; // |TextInputPluginDelegate| void OnCursorRectUpdated(const Rect& rect) override; @@ -161,15 +169,21 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, std::unique_ptr handler); private: - // Struct holding the mouse state. The engine doesn't keep track of which - // mouse buttons have been pressed, so it's the embedding's responsibility. - struct MouseState { - // True if the last event sent to Flutter had at least one mouse button. - // pressed. + // Struct holding the state of an individual pointer. The engine doesn't keep + // track of which buttons have been pressed, so it's the embedding's + // responsibility. + struct PointerState { + // The device kind. + FlutterPointerDeviceKind device_kind = kFlutterPointerDeviceKindMouse; + + // A virtual pointer ID that is unique across all device kinds. + int32_t pointer_id = 0; + + // True if the last event sent to Flutter had at least one button pressed. bool flutter_state_is_down = false; // True if kAdd has been sent to Flutter. Used to determine whether - // to send a kAdd event before sending an incoming mouse event, since + // to send a kAdd event before sending an incoming pointer event, since // Flutter expects pointers to be added before events are sent for them. bool flutter_state_is_added = false; @@ -195,24 +209,20 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, void SendWindowMetrics(size_t width, size_t height, double dpiscale) const; // Reports a mouse movement to Flutter engine. - void SendPointerMove(double x, - double y, - FlutterPointerDeviceKind device_kind); + void SendPointerMove(double x, double y, PointerState* state); // Reports mouse press to Flutter engine. - void SendPointerDown(double x, - double y, - FlutterPointerDeviceKind device_kind); + void SendPointerDown(double x, double y, PointerState* state); // Reports mouse release to Flutter engine. - void SendPointerUp(double x, double y, FlutterPointerDeviceKind device_kind); + void SendPointerUp(double x, double y, PointerState* state); // Reports mouse left the window client area. // // Win32 api doesn't have "mouse enter" event. Therefore, there is no // SendPointerEnter method. A mouse enter event is tracked then the "move" // event is called. - void SendPointerLeave(FlutterPointerDeviceKind device_kind); + void SendPointerLeave(PointerState* state); // Reports a keyboard character to Flutter engine. void SendText(const std::u16string&); @@ -255,35 +265,24 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, double y, double delta_x, double delta_y, - int scroll_offset_multiplier); + int scroll_offset_multiplier, + FlutterPointerDeviceKind device_kind, + int32_t device_id); + + // Creates a PointerState object unless it already exists. + PointerState* GetOrCreatePointerState(FlutterPointerDeviceKind device_kind, + int32_t device_id); // Sets |event_data|'s phase to either kMove or kHover depending on the // current primary mouse button state. - void SetEventPhaseFromCursorButtonState( - FlutterPointerEvent* event_data) const; + void SetEventPhaseFromCursorButtonState(FlutterPointerEvent* event_data, + const PointerState* state) const; // Sends a pointer event to the Flutter engine based on given data. Since // all input messages are passed in physical pixel values, no translation is // needed before passing on to engine. - void SendPointerEventWithData(const FlutterPointerEvent& event_data); - - // Resets the mouse state to its default values. - void ResetMouseState() { mouse_state_ = MouseState(); } - - // Updates the mouse state to whether the last event to Flutter had at least - // one mouse button pressed. - void SetMouseFlutterStateDown(bool is_down) { - mouse_state_.flutter_state_is_down = is_down; - } - - // Updates the mouse state to whether the last event to Flutter was a kAdd - // event. - void SetMouseFlutterStateAdded(bool is_added) { - mouse_state_.flutter_state_is_added = is_added; - } - - // Updates the currently pressed buttons. - void SetMouseButtons(uint64_t buttons) { mouse_state_.buttons = buttons; } + void SendPointerEventWithData(const FlutterPointerEvent& event_data, + PointerState* state); // Currently configured WindowsRenderTarget for this view used by // surface_manager for creation of render surfaces and bound to the physical @@ -293,8 +292,8 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, // The engine associated with this view. std::unique_ptr engine_; - // Keeps track of mouse state in relation to the window. - MouseState mouse_state_; + // Keeps track of pointer states in relation to the window. + std::map> pointer_states_; // The plugin registrar managing internal plugins. std::unique_ptr internal_plugin_registrar_; diff --git a/engine/src/flutter/shell/platform/windows/game_pad_cursor_winuwp.cc b/engine/src/flutter/shell/platform/windows/game_pad_cursor_winuwp.cc index 676b5350445..f4edb195a6a 100644 --- a/engine/src/flutter/shell/platform/windows/game_pad_cursor_winuwp.cc +++ b/engine/src/flutter/shell/platform/windows/game_pad_cursor_winuwp.cc @@ -7,6 +7,8 @@ namespace flutter { +static constexpr int32_t kDefaultPointerDeviceId = 0; + GamepadCursorWinUWP::GamepadCursorWinUWP( WindowBindingHandlerDelegate* view, DisplayHelperWinUWP* displayhelper, @@ -162,16 +164,18 @@ void GamepadCursorWinUWP::OnGamepadLeftStickMoved(double x, double y) { // TODO(dnfield): Support for gamepad as a distinct device type? // https://github.com/flutter/flutter/issues/80472 binding_handler_delegate_->OnPointerMove(scaled.x, scaled.y, - kFlutterPointerDeviceKindMouse); + kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId); } } void GamepadCursorWinUWP::OnGamepadRightStickMoved(double x, double y) { winrt::Windows::Foundation::Numerics::float3 scaled = GetScaledInput(cursor_visual_.Offset()); - binding_handler_delegate_->OnScroll(scaled.x, scaled.y, - x * kControllerScrollMultiplier, - y * kControllerScrollMultiplier, 1); + binding_handler_delegate_->OnScroll( + scaled.x, scaled.y, x * kControllerScrollMultiplier, + y * kControllerScrollMultiplier, 1, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId); } void GamepadCursorWinUWP::OnGamepadButtonPressed( @@ -190,6 +194,7 @@ void GamepadCursorWinUWP::OnGamepadButtonPressed( // https://github.com/flutter/flutter/issues/80472 binding_handler_delegate_->OnPointerDown( scaled.x, scaled.y, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, FlutterPointerMouseButtons::kFlutterPointerButtonMousePrimary); } else if ((buttons & winrt::Windows::Gaming::Input::GamepadButtons::DPadLeft) == @@ -234,6 +239,7 @@ void GamepadCursorWinUWP::OnGamepadButtonReleased( // https://github.com/flutter/flutter/issues/80472 binding_handler_delegate_->OnPointerUp( scaled.x, scaled.y, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, FlutterPointerMouseButtons::kFlutterPointerButtonMousePrimary); } else if ((buttons & winrt::Windows::Gaming::Input::GamepadButtons::DPadLeft) == diff --git a/engine/src/flutter/shell/platform/windows/sequential_id_generator.cc b/engine/src/flutter/shell/platform/windows/sequential_id_generator.cc new file mode 100644 index 00000000000..ea8877a5cd1 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/sequential_id_generator.cc @@ -0,0 +1,77 @@ +// 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/sequential_id_generator.h" + +namespace flutter { + +namespace { + +// Removes |key| from |first|, and |first[key]| from |second|. +template +void Remove(uint32_t key, T* first, T* second) { + auto iter = first->find(key); + if (iter == first->end()) + return; + + uint32_t second_key = iter->second; + first->erase(iter); + + iter = second->find(second_key); + second->erase(iter); +} + +} // namespace + +SequentialIdGenerator::SequentialIdGenerator(uint32_t min_id, uint32_t max_id) + : min_id_(min_id), min_available_id_(min_id), max_id_(max_id) {} + +SequentialIdGenerator::~SequentialIdGenerator() {} + +uint32_t SequentialIdGenerator::GetGeneratedId(uint32_t number) { + auto it = number_to_id_.find(number); + if (it != number_to_id_.end()) + return it->second; + + auto id = GetNextAvailableId(); + number_to_id_.emplace(number, id); + id_to_number_.emplace(id, number); + return id; +} + +bool SequentialIdGenerator::HasGeneratedIdFor(uint32_t number) const { + return number_to_id_.find(number) != number_to_id_.end(); +} + +void SequentialIdGenerator::ReleaseNumber(uint32_t number) { + if (number_to_id_.count(number) > 0U) { + UpdateNextAvailableIdAfterRelease(number_to_id_[number]); + Remove(number, &number_to_id_, &id_to_number_); + } +} + +void SequentialIdGenerator::ReleaseId(uint32_t id) { + if (id_to_number_.count(id) > 0U) { + UpdateNextAvailableIdAfterRelease(id); + Remove(id_to_number_[id], &number_to_id_, &id_to_number_); + } +} + +uint32_t SequentialIdGenerator::GetNextAvailableId() { + while (id_to_number_.count(min_available_id_) > 0 && + min_available_id_ < max_id_) { + ++min_available_id_; + } + if (min_available_id_ >= max_id_) + min_available_id_ = min_id_; + return min_available_id_; +} + +void SequentialIdGenerator::UpdateNextAvailableIdAfterRelease(uint32_t id) { + if (id < min_available_id_) { + min_available_id_ = id; + } +} + +} // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/sequential_id_generator.h b/engine/src/flutter/shell/platform/windows/sequential_id_generator.h new file mode 100644 index 00000000000..a7850b499d8 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/sequential_id_generator.h @@ -0,0 +1,59 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_SEQUENTIAL_ID_GENERATOR_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_SEQUENTIAL_ID_GENERATOR_H_ + +#include +#include + +namespace flutter { + +// This is used to generate a series of sequential ID numbers in a way that a +// new ID is always the lowest possible ID in the sequence. +// +// based on +// https://source.chromium.org/chromium/chromium/src/+/master:ui/gfx/sequential_id_generator.h +class SequentialIdGenerator { + public: + // Creates a new generator with the specified lower bound and uppoer bound for + // the IDs. + explicit SequentialIdGenerator(uint32_t min_id, uint32_t max_id); + ~SequentialIdGenerator(); + + // Generates a unique ID to represent |number|. The generated ID is the + // smallest available ID greater than or equal to the |min_id| specified + // during creation of the generator. + uint32_t GetGeneratedId(uint32_t number); + + // Checks to see if the generator currently has a unique ID generated for + // |number|. + bool HasGeneratedIdFor(uint32_t number) const; + + // Removes the ID previously generated for |number| by calling + // |GetGeneratedID()| - does nothing if the number is not mapped. + void ReleaseNumber(uint32_t number); + + // Releases ID previously generated by calling |GetGeneratedID()|. Does + // nothing if the ID is not mapped. + void ReleaseId(uint32_t id); + + private: + typedef std::unordered_map IdMap; + + uint32_t GetNextAvailableId(); + + void UpdateNextAvailableIdAfterRelease(uint32_t id); + + IdMap number_to_id_; + IdMap id_to_number_; + + const uint32_t min_id_; + const uint32_t max_id_; + uint32_t min_available_id_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_SEQUENTIAL_ID_GENERATOR_H_ diff --git a/engine/src/flutter/shell/platform/windows/sequential_id_generator_unittests.cc b/engine/src/flutter/shell/platform/windows/sequential_id_generator_unittests.cc new file mode 100644 index 00000000000..ea1864b2b05 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/sequential_id_generator_unittests.cc @@ -0,0 +1,49 @@ +// 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/sequential_id_generator.h" + +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +TEST(SequentialIdGeneratorTest, RemoveMultipleNumbers) { + const uint32_t kMinId = 4; + const uint32_t kMaxId = 128; + + SequentialIdGenerator generator(kMinId, kMaxId); + + EXPECT_EQ(4U, generator.GetGeneratedId(45)); + EXPECT_EQ(5U, generator.GetGeneratedId(55)); + EXPECT_EQ(6U, generator.GetGeneratedId(15)); + + generator.ReleaseNumber(45); + EXPECT_FALSE(generator.HasGeneratedIdFor(45)); + generator.ReleaseNumber(15); + EXPECT_FALSE(generator.HasGeneratedIdFor(15)); + + EXPECT_EQ(5U, generator.GetGeneratedId(55)); + EXPECT_EQ(4U, generator.GetGeneratedId(12)); + + generator.ReleaseNumber(12); + generator.ReleaseNumber(55); + EXPECT_EQ(4U, generator.GetGeneratedId(0)); +} + +TEST(SequentialIdGeneratorTest, MaybeRemoveNumbers) { + const uint32_t kMinId = 0; + const uint32_t kMaxId = 128; + + SequentialIdGenerator generator(kMinId, kMaxId); + + EXPECT_EQ(0U, generator.GetGeneratedId(42)); + + generator.ReleaseNumber(42); + EXPECT_FALSE(generator.HasGeneratedIdFor(42)); + generator.ReleaseNumber(42); +} + +} // namespace testing +} // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/testing/mock_window_win32.h b/engine/src/flutter/shell/platform/windows/testing/mock_window_win32.h index d3938e7762c..b53e9f7c690 100644 --- a/engine/src/flutter/shell/platform/windows/testing/mock_window_win32.h +++ b/engine/src/flutter/shell/platform/windows/testing/mock_window_win32.h @@ -34,14 +34,18 @@ class MockWin32Window : public WindowWin32, public MockMessageQueue { MOCK_METHOD1(OnDpiScale, void(unsigned int)); MOCK_METHOD2(OnResize, void(unsigned int, unsigned int)); - MOCK_METHOD2(OnPointerMove, void(double, double)); - MOCK_METHOD3(OnPointerDown, void(double, double, UINT)); - MOCK_METHOD3(OnPointerUp, void(double, double, UINT)); - MOCK_METHOD0(OnPointerLeave, void()); + MOCK_METHOD4(OnPointerMove, + void(double, double, FlutterPointerDeviceKind, int32_t)); + MOCK_METHOD5(OnPointerDown, + void(double, double, FlutterPointerDeviceKind, int32_t, UINT)); + MOCK_METHOD5(OnPointerUp, + void(double, double, FlutterPointerDeviceKind, int32_t, UINT)); + MOCK_METHOD2(OnPointerLeave, void(FlutterPointerDeviceKind, int32_t)); MOCK_METHOD0(OnSetCursor, void()); MOCK_METHOD1(OnText, void(const std::u16string&)); MOCK_METHOD6(OnKey, bool(int, int, int, char32_t, bool, bool)); - MOCK_METHOD2(OnScroll, void(double, double)); + MOCK_METHOD4(OnScroll, + void(double, double, FlutterPointerDeviceKind, int32_t)); MOCK_METHOD0(OnComposeBegin, void()); MOCK_METHOD0(OnComposeCommit, void()); MOCK_METHOD0(OnComposeEnd, void()); diff --git a/engine/src/flutter/shell/platform/windows/window_binding_handler_delegate.h b/engine/src/flutter/shell/platform/windows/window_binding_handler_delegate.h index 420bb9704d7..5cf8fadd897 100644 --- a/engine/src/flutter/shell/platform/windows/window_binding_handler_delegate.h +++ b/engine/src/flutter/shell/platform/windows/window_binding_handler_delegate.h @@ -21,13 +21,15 @@ class WindowBindingHandlerDelegate { // Typically called by currently configured WindowBindingHandler virtual void OnPointerMove(double x, double y, - FlutterPointerDeviceKind device_kind) = 0; + FlutterPointerDeviceKind device_kind, + int32_t device_id) = 0; // Notifies delegate that backing window mouse pointer button has been // pressed. Typically called by currently configured WindowBindingHandler virtual void OnPointerDown(double x, double y, FlutterPointerDeviceKind device_kind, + int32_t device_id, FlutterPointerMouseButtons button) = 0; // Notifies delegate that backing window mouse pointer button has been @@ -35,11 +37,13 @@ class WindowBindingHandlerDelegate { virtual void OnPointerUp(double x, double y, FlutterPointerDeviceKind device_kind, + int32_t device_id, FlutterPointerMouseButtons button) = 0; // Notifies delegate that backing window mouse pointer has left the window. // Typically called by currently configured WindowBindingHandler - virtual void OnPointerLeave(FlutterPointerDeviceKind device_kind) = 0; + virtual void OnPointerLeave(FlutterPointerDeviceKind device_kind, + int32_t device_id) = 0; // Notifies delegate that backing window has received text. // Typically called by currently configured WindowBindingHandler @@ -88,7 +92,9 @@ class WindowBindingHandlerDelegate { double y, double delta_x, double delta_y, - int scroll_offset_multiplier) = 0; + int scroll_offset_multiplier, + FlutterPointerDeviceKind device_kind, + int32_t device_id) = 0; }; } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/window_win32.cc b/engine/src/flutter/shell/platform/windows/window_win32.cc index d20a166bdd1..49a58cbb8d3 100644 --- a/engine/src/flutter/shell/platform/windows/window_win32.cc +++ b/engine/src/flutter/shell/platform/windows/window_win32.cc @@ -13,13 +13,39 @@ namespace flutter { namespace { + +static constexpr int32_t kDefaultPointerDeviceId = 0; + +// This method is only valid during a window message related to mouse/touch +// input. +// See +// https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages?redirectedfrom=MSDN#distinguishing-pen-input-from-mouse-and-touch. +static FlutterPointerDeviceKind GetFlutterPointerDeviceKind() { + constexpr LPARAM kTouchOrPenSignature = 0xFF515700; + constexpr LPARAM kTouchSignature = kTouchOrPenSignature | 0x80; + constexpr LPARAM kSignatureMask = 0xFFFFFF00; + LPARAM info = GetMessageExtraInfo(); + if ((info & kSignatureMask) == kTouchOrPenSignature) { + if ((info & kTouchSignature) == kTouchSignature) { + return kFlutterPointerDeviceKindTouch; + } + return kFlutterPointerDeviceKindStylus; + } + return kFlutterPointerDeviceKindMouse; +} + char32_t CodePointFromSurrogatePair(wchar_t high, wchar_t low) { return 0x10000 + ((static_cast(high) & 0x000003FF) << 10) + (low & 0x3FF); } + +static const int kMinTouchDeviceId = 0; +static const int kMaxTouchDeviceId = 128; + } // namespace -WindowWin32::WindowWin32() { +WindowWin32::WindowWin32() + : touch_id_generator_(kMinTouchDeviceId, kMaxTouchDeviceId) { // Get the DPI of the primary monitor as the initial DPI. If Per-Monitor V2 is // supported, |current_dpi_| should be updated in the // kWmDpiChangedBeforeParent message. @@ -94,6 +120,7 @@ LRESULT CALLBACK WindowWin32::WndProc(HWND const window, auto that = static_cast(cs->lpCreateParams); that->window_handle_ = window; that->text_input_manager_.SetWindowHandle(window); + RegisterTouchWindow(window, 0); } else if (WindowWin32* that = GetThisFromHandle(window)) { return that->HandleMessage(message, wparam, lparam); } @@ -204,6 +231,7 @@ WindowWin32::HandleMessage(UINT const message, int xPos = 0, yPos = 0; UINT width = 0, height = 0; UINT button_pressed = 0; + FlutterPointerDeviceKind device_kind; switch (message) { case kWmDpiChangedBeforeParent: @@ -218,15 +246,56 @@ WindowWin32::HandleMessage(UINT const message, current_height_ = height; HandleResize(width, height); break; - case WM_MOUSEMOVE: - TrackMouseLeaveEvent(window_handle_); + case WM_TOUCH: { + UINT num_points = LOWORD(wparam); + touch_points_.resize(num_points); + auto touch_input_handle = reinterpret_cast(lparam); + if (GetTouchInputInfo(touch_input_handle, num_points, + touch_points_.data(), sizeof(TOUCHINPUT))) { + for (const auto& touch : touch_points_) { + // Generate a mapped ID for the Windows-provided touch ID + auto touch_id = touch_id_generator_.GetGeneratedId(touch.dwID); - xPos = GET_X_LPARAM(lparam); - yPos = GET_Y_LPARAM(lparam); - OnPointerMove(static_cast(xPos), static_cast(yPos)); + POINT pt = {TOUCH_COORD_TO_PIXEL(touch.x), + TOUCH_COORD_TO_PIXEL(touch.y)}; + ScreenToClient(window_handle_, &pt); + auto x = static_cast(pt.x); + auto y = static_cast(pt.y); + + if (touch.dwFlags & TOUCHEVENTF_DOWN) { + OnPointerDown(x, y, kFlutterPointerDeviceKindTouch, touch_id, + WM_LBUTTONDOWN); + } else if (touch.dwFlags & TOUCHEVENTF_MOVE) { + OnPointerMove(x, y, kFlutterPointerDeviceKindTouch, touch_id); + } else if (touch.dwFlags & TOUCHEVENTF_UP) { + OnPointerUp(x, y, kFlutterPointerDeviceKindTouch, touch_id, + WM_LBUTTONDOWN); + OnPointerLeave(kFlutterPointerDeviceKindTouch, touch_id); + touch_id_generator_.ReleaseNumber(touch.dwID); + } + } + CloseTouchInputHandle(touch_input_handle); + } + return 0; + } + case WM_MOUSEMOVE: + device_kind = GetFlutterPointerDeviceKind(); + if (device_kind == kFlutterPointerDeviceKindMouse) { + TrackMouseLeaveEvent(window_handle_); + + xPos = GET_X_LPARAM(lparam); + yPos = GET_Y_LPARAM(lparam); + + OnPointerMove(static_cast(xPos), static_cast(yPos), + device_kind, kDefaultPointerDeviceId); + } break; - case WM_MOUSELEAVE:; - OnPointerLeave(); + case WM_MOUSELEAVE: + device_kind = GetFlutterPointerDeviceKind(); + if (device_kind == kFlutterPointerDeviceKindMouse) { + OnPointerLeave(device_kind, kDefaultPointerDeviceId); + } + // Once the tracked event is received, the TrackMouseEvent function // resets. Set to false to make sure it's called once mouse movement is // detected again. @@ -250,6 +319,11 @@ WindowWin32::HandleMessage(UINT const message, case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case WM_XBUTTONDOWN: + device_kind = GetFlutterPointerDeviceKind(); + if (device_kind != kFlutterPointerDeviceKindMouse) { + break; + } + if (message == WM_LBUTTONDOWN) { // Capture the pointer in case the user drags outside the client area. // In this case, the "mouse leave" event is delayed until the user @@ -265,12 +339,17 @@ WindowWin32::HandleMessage(UINT const message, xPos = GET_X_LPARAM(lparam); yPos = GET_Y_LPARAM(lparam); OnPointerDown(static_cast(xPos), static_cast(yPos), - button_pressed); + device_kind, kDefaultPointerDeviceId, button_pressed); break; case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case WM_XBUTTONUP: + device_kind = GetFlutterPointerDeviceKind(); + if (device_kind != kFlutterPointerDeviceKindMouse) { + break; + } + if (message == WM_LBUTTONUP) { ReleaseCapture(); } @@ -281,16 +360,18 @@ WindowWin32::HandleMessage(UINT const message, xPos = GET_X_LPARAM(lparam); yPos = GET_Y_LPARAM(lparam); OnPointerUp(static_cast(xPos), static_cast(yPos), - button_pressed); + device_kind, kDefaultPointerDeviceId, button_pressed); break; case WM_MOUSEWHEEL: - OnScroll(0.0, -(static_cast(HIWORD(wparam)) / - static_cast(WHEEL_DELTA))); + OnScroll(0.0, + -(static_cast(HIWORD(wparam)) / + static_cast(WHEEL_DELTA)), + kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId); break; case WM_MOUSEHWHEEL: OnScroll((static_cast(HIWORD(wparam)) / static_cast(WHEEL_DELTA)), - 0.0); + 0.0, kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId); break; case WM_INPUTLANGCHANGE: // TODO(cbracken): pass this to TextInputManager to aid with diff --git a/engine/src/flutter/shell/platform/windows/window_win32.h b/engine/src/flutter/shell/platform/windows/window_win32.h index 5fcedeaf0d6..a5834a5df57 100644 --- a/engine/src/flutter/shell/platform/windows/window_win32.h +++ b/engine/src/flutter/shell/platform/windows/window_win32.h @@ -11,7 +11,10 @@ #include #include #include +#include +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/windows/sequential_id_generator.h" #include "flutter/shell/platform/windows/text_input_manager_win32.h" namespace flutter { @@ -54,17 +57,18 @@ class WindowWin32 { // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. - LRESULT - HandleMessage(UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; + LRESULT HandleMessage(UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; // When WM_DPICHANGE process it using |hWnd|, |wParam|. If // |top_level| is set, extract the suggested new size from |lParam| and resize // the window to the new suggested size. If |top_level| is not set, the // |lParam| will not contain a suggested size hence ignore it. - LRESULT - HandleDpiChange(HWND hWnd, WPARAM wParam, LPARAM lParam, bool top_level); + LRESULT HandleDpiChange(HWND hWnd, + WPARAM wParam, + LPARAM lParam, + bool top_level); // Called when the DPI changes either when a // user drags the window between monitors of differing DPI or when the user @@ -76,17 +80,29 @@ class WindowWin32 { // Called when the pointer moves within the // window bounds. - virtual void OnPointerMove(double x, double y) = 0; + virtual void OnPointerMove(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id) = 0; // Called when the a mouse button, determined by |button|, goes down. - virtual void OnPointerDown(double x, double y, UINT button) = 0; + virtual void OnPointerDown(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id, + UINT button) = 0; // Called when the a mouse button, determined by |button|, goes from // down to up - virtual void OnPointerUp(double x, double y, UINT button) = 0; + virtual void OnPointerUp(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id, + UINT button) = 0; // Called when the mouse leaves the window. - virtual void OnPointerLeave() = 0; + virtual void OnPointerLeave(FlutterPointerDeviceKind device_kind, + int32_t device_id) = 0; // Called when the cursor should be set for the client area. virtual void OnSetCursor() = 0; @@ -151,7 +167,10 @@ class WindowWin32 { virtual void UpdateCursorRect(const Rect& rect); // Called when mouse scrollwheel input occurs. - virtual void OnScroll(double delta_x, double delta_y) = 0; + virtual void OnScroll(double delta_x, + double delta_y, + FlutterPointerDeviceKind device_kind, + int32_t device_id) = 0; UINT GetCurrentDPI(); @@ -226,6 +245,12 @@ class WindowWin32 { // Manages IME state. TextInputManagerWin32 text_input_manager_; + + // Used for temporarily storing the WM_TOUCH-provided touch points. + std::vector touch_points_; + + // Generates touch point IDs for touch events. + SequentialIdGenerator touch_id_generator_; }; } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/window_win32_unittests.cc b/engine/src/flutter/shell/platform/windows/window_win32_unittests.cc index 58f9e027a87..92fbf009fd6 100644 --- a/engine/src/flutter/shell/platform/windows/window_win32_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/window_win32_unittests.cc @@ -25,7 +25,9 @@ TEST(MockWin32Window, VerticalScroll) { const int scroll_amount = 10; // Vertical scroll should be passed along, adjusted for scroll tick size // and direction. - EXPECT_CALL(window, OnScroll(0, -scroll_amount / 120.0)).Times(1); + EXPECT_CALL(window, OnScroll(0, -scroll_amount / 120.0, + kFlutterPointerDeviceKindMouse, 0)) + .Times(1); window.InjectWindowMessage(WM_MOUSEWHEEL, MAKEWPARAM(0, scroll_amount), 0); } @@ -34,7 +36,9 @@ TEST(MockWin32Window, HorizontalScroll) { MockWin32Window window; const int scroll_amount = 10; // Vertical scroll should be passed along, adjusted for scroll tick size. - EXPECT_CALL(window, OnScroll(scroll_amount / 120.0, 0)).Times(1); + EXPECT_CALL(window, OnScroll(scroll_amount / 120.0, 0, + kFlutterPointerDeviceKindMouse, 0)) + .Times(1); window.InjectWindowMessage(WM_MOUSEHWHEEL, MAKEWPARAM(0, scroll_amount), 0); }