From d992ac83069369e051897b34a2611c48315a8ed4 Mon Sep 17 00:00:00 2001 From: Callum Moffat Date: Sun, 12 Sep 2021 02:12:48 -0400 Subject: [PATCH] Implement trackpad gestures in engine --- lib/ui/platform_dispatcher.dart | 8 +- lib/ui/pointer.dart | 53 ++- lib/ui/window/pointer_data.h | 16 +- .../window/pointer_data_packet_converter.cc | 92 ++++++ lib/ui/window/pointer_data_packet_converter.h | 5 + ...pointer_data_packet_converter_unittests.cc | 96 ++++++ lib/web_ui/lib/src/ui/pointer.dart | 23 +- .../android/AndroidTouchProcessor.java | 103 ++++-- .../framework/Headers/FlutterViewController.h | 6 +- .../framework/Source/FlutterViewController.mm | 79 ++++- .../framework/Source/FlutterViewController.mm | 129 +++++++- shell/platform/embedder/embedder.cc | 16 + shell/platform/embedder/embedder.h | 14 + shell/platform/linux/fl_engine.cc | 31 ++ shell/platform/linux/fl_engine_private.h | 11 + shell/platform/linux/fl_engine_test.cc | 37 +++ shell/platform/linux/fl_view.cc | 175 +++++++++- shell/platform/windows/BUILD.gn | 18 +- shell/platform/windows/direct_manipulation.cc | 310 ++++++++++++++++++ shell/platform/windows/direct_manipulation.h | 83 +++++ .../windows/direct_manipulation_unittests.cc | 195 +++++++++++ .../platform/windows/flutter_window_win32.cc | 1 + .../windows/flutter_window_win32_unittests.cc | 44 +-- .../platform/windows/flutter_windows_view.cc | 71 ++++ shell/platform/windows/flutter_windows_view.h | 28 ++ .../mock_window_binding_handler_delegate.h | 64 ++++ .../windows/window_binding_handler_delegate.h | 8 + shell/platform/windows/window_win32.cc | 32 ++ shell/platform/windows/window_win32.h | 6 + 29 files changed, 1640 insertions(+), 114 deletions(-) create mode 100644 shell/platform/windows/direct_manipulation.cc create mode 100644 shell/platform/windows/direct_manipulation.h create mode 100644 shell/platform/windows/direct_manipulation_unittests.cc create mode 100644 shell/platform/windows/testing/mock_window_binding_handler_delegate.h diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index cc0bdcdfb14b9..a7e203a71c13b 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -303,7 +303,7 @@ class PlatformDispatcher { // * pointer_data.cc // * pointer.dart // * AndroidTouchProcessor.java - static const int _kPointerDataFieldCount = 29; + static const int _kPointerDataFieldCount = 35; static PointerDataPacket _unpackPointerDataPacket(ByteData packet) { const int kStride = Int64List.bytesPerElement; @@ -343,6 +343,12 @@ class PlatformDispatcher { platformData: packet.getInt64(kStride * offset++, _kFakeHostEndian), scrollDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), scrollDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + panX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + panY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + panDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + panDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + scale: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + angle: packet.getFloat64(kStride * offset++, _kFakeHostEndian), )); assert(offset == (i + 1) * _kPointerDataFieldCount); } diff --git a/lib/ui/pointer.dart b/lib/ui/pointer.dart index 1b2bafbd11e7e..fc40e2fbf5110 100644 --- a/lib/ui/pointer.dart +++ b/lib/ui/pointer.dart @@ -36,6 +36,15 @@ enum PointerChange { /// The pointer has stopped making contact with the device. up, + + /// An offscreen interaction gesture has started (such as trackpad gesture) + flowStart, + + /// An offscreen interaction has changed + flowUpdate, + + /// An offscreen interaction has ended + flowEnd, } /// The kind of pointer device. @@ -101,6 +110,12 @@ class PointerData { this.platformData = 0, this.scrollDeltaX = 0.0, this.scrollDeltaY = 0.0, + this.panX = 0.0, + this.panY = 0.0, + this.panDeltaX = 0.0, + this.panDeltaY = 0.0, + this.scale = 0.0, + this.angle = 0.0, }); /// Unique identifier that ties the [PointerEvent] to embedder event created it. @@ -265,6 +280,36 @@ class PointerData { /// The amount to scroll in the y direction, in physical pixels. final double scrollDeltaY; + /// For events with change of PointerChange.flowUpdate: + /// + /// The current panning magnitude of the gesture in the x direction, in physical pixels. + final double panX; + + /// For events with change of PointerChange.flowUpdate: + /// + /// The current panning magnitude of the gesture in the x direction, in physical pixels. + final double panY; + + /// For events with change of PointerChange.flowUpdate: + /// + /// The difference in panning of the gesture in the x direction since the latest flowUpdate event, in physical pixels. + final double panDeltaX; + + /// For events with change of PointerChange.flowUpdate: + /// + /// The difference in panning of the gesture in the y direction since the last flowUpdate event, in physical pixels. + final double panDeltaY; + + /// For events with change of PointerChange.flowUpdate: + /// + /// The current scale of the gesture (unitless), with 1.0 as the initial scale. + final double scale; + + /// For events with change of PointerChange.flowUpdate: + /// + /// The current angle of the gesture in radians, with 0.0 as the initial angle. + final double angle; + @override String toString() => 'PointerData(x: $physicalX, y: $physicalY)'; @@ -298,7 +343,13 @@ class PointerData { 'tilt: $tilt, ' 'platformData: $platformData, ' 'scrollDeltaX: $scrollDeltaX, ' - 'scrollDeltaY: $scrollDeltaY' + 'scrollDeltaY: $scrollDeltaY, ' + 'panX: $panX, ' + 'panY: $panY, ' + 'panDeltaX: $panDeltaX, ' + 'panDeltaY: $panDeltaY, ' + 'scale: $scale, ' + 'angle: $angle' ')'; } } diff --git a/lib/ui/window/pointer_data.h b/lib/ui/window/pointer_data.h index 216cf755ea0eb..d74c1a5df05b7 100644 --- a/lib/ui/window/pointer_data.h +++ b/lib/ui/window/pointer_data.h @@ -10,7 +10,7 @@ namespace flutter { // If this value changes, update the pointer data unpacking code in hooks.dart. -static constexpr int kPointerDataFieldCount = 29; +static constexpr int kPointerDataFieldCount = 35; static constexpr int kBytesPerField = sizeof(int64_t); // Must match the button constants in events.dart. enum PointerButtonMouse : int64_t { @@ -42,6 +42,9 @@ struct alignas(8) PointerData { kDown, kMove, kUp, + kFlowStart, + kFlowUpdate, + kFlowEnd, }; // Must match the PointerDeviceKind enum in pointer.dart. @@ -53,10 +56,7 @@ struct alignas(8) PointerData { }; // Must match the PointerSignalKind enum in pointer.dart. - enum class SignalKind : int64_t { - kNone, - kScroll, - }; + enum class SignalKind : int64_t { kNone, kScroll }; int64_t embedder_id; int64_t time_stamp; @@ -87,6 +87,12 @@ struct alignas(8) PointerData { int64_t platformData; double scroll_delta_x; double scroll_delta_y; + double pan_x; + double pan_y; + double pan_delta_x; + double pan_delta_y; + double scale; + double angle; void Clear(); }; diff --git a/lib/ui/window/pointer_data_packet_converter.cc b/lib/ui/window/pointer_data_packet_converter.cc index 8f90023d5b939..ac29e793d689a 100644 --- a/lib/ui/window/pointer_data_packet_converter.cc +++ b/lib/ui/window/pointer_data_packet_converter.cc @@ -4,6 +4,7 @@ #include "flutter/lib/ui/window/pointer_data_packet_converter.h" +#include #include #include "flutter/fml/logging.h" @@ -207,6 +208,88 @@ void PointerDataPacketConverter::ConvertPointerData( converted_pointers.push_back(pointer_data); break; } + case PointerData::Change::kFlowStart: { + // Makes sure we have an existing pointer + auto iter = states_.find(pointer_data.device); + PointerState state; + if (iter == states_.end()) { + // Synthesizes add event if the pointer is not previously added. + PointerData synthesized_add_event = pointer_data; + synthesized_add_event.change = PointerData::Change::kAdd; + synthesized_add_event.synthesized = 1; + synthesized_add_event.buttons = 0; + state = EnsurePointerState(synthesized_add_event); + converted_pointers.push_back(synthesized_add_event); + } else { + state = iter->second; + } + FML_DCHECK(!state.is_down); + FML_DCHECK(!state.is_flow_active); + if (LocationNeedsUpdate(pointer_data, state)) { + // Synthesizes a hover event if the location does not match. + PointerData synthesized_hover_event = pointer_data; + synthesized_hover_event.change = PointerData::Change::kHover; + synthesized_hover_event.synthesized = 1; + synthesized_hover_event.buttons = 0; + + UpdateDeltaAndState(synthesized_hover_event, state); + converted_pointers.push_back(synthesized_hover_event); + } + + UpdatePointerIdentifier(pointer_data, state, true); + state.is_flow_active = true; + state.pan_x = 0; + state.pan_y = 0; + state.scale = 1; + state.angle = 0; + states_[pointer_data.device] = state; + converted_pointers.push_back(pointer_data); + break; + } + case PointerData::Change::kFlowUpdate: { + // Makes sure we have an existing pointer in flow_active state + auto iter = states_.find(pointer_data.device); + FML_DCHECK(iter != states_.end()); + PointerState state = iter->second; + FML_DCHECK(!state.is_down); + FML_DCHECK(state.is_flow_active); + + UpdatePointerIdentifier(pointer_data, state, false); + UpdateDeltaAndState(pointer_data, state); + + converted_pointers.push_back(pointer_data); + break; + } + case PointerData::Change::kFlowEnd: { + // Makes sure we have an existing pointer in flow_active state + auto iter = states_.find(pointer_data.device); + FML_DCHECK(iter != states_.end()); + PointerState state = iter->second; + FML_DCHECK(state.is_flow_active); + + UpdatePointerIdentifier(pointer_data, state, false); + + if (LocationNeedsUpdate(pointer_data, state)) { + // Synthesizes an update event if the location does not match. + PointerData synthesized_move_event = pointer_data; + synthesized_move_event.change = PointerData::Change::kFlowUpdate; + synthesized_move_event.pan_x = state.pan_x; + synthesized_move_event.pan_y = state.pan_y; + synthesized_move_event.pan_delta_x = 0; + synthesized_move_event.pan_delta_y = 0; + synthesized_move_event.scale = state.scale; + synthesized_move_event.angle = state.angle; + synthesized_move_event.synthesized = 1; + + UpdateDeltaAndState(synthesized_move_event, state); + converted_pointers.push_back(synthesized_move_event); + } + + state.is_flow_active = false; + states_[pointer_data.device] = state; + converted_pointers.push_back(pointer_data); + break; + } default: { converted_pointers.push_back(pointer_data); break; @@ -261,8 +344,11 @@ PointerState PointerDataPacketConverter::EnsurePointerState( PointerState state; state.pointer_identifier = 0; state.is_down = false; + state.is_flow_active = false; state.physical_x = pointer_data.physical_x; state.physical_y = pointer_data.physical_y; + state.pan_x = pointer_data.pan_x; + state.pan_y = pointer_data.pan_y; states_[pointer_data.device] = state; return state; } @@ -271,8 +357,14 @@ void PointerDataPacketConverter::UpdateDeltaAndState(PointerData& pointer_data, PointerState& state) { pointer_data.physical_delta_x = pointer_data.physical_x - state.physical_x; pointer_data.physical_delta_y = pointer_data.physical_y - state.physical_y; + pointer_data.pan_delta_x = pointer_data.pan_x - state.pan_x; + pointer_data.pan_delta_y = pointer_data.pan_y - state.pan_y; state.physical_x = pointer_data.physical_x; state.physical_y = pointer_data.physical_y; + state.pan_x = pointer_data.pan_x; + state.pan_y = pointer_data.pan_y; + state.scale = pointer_data.scale; + state.angle = pointer_data.angle; states_[pointer_data.device] = state; } diff --git a/lib/ui/window/pointer_data_packet_converter.h b/lib/ui/window/pointer_data_packet_converter.h index c5871d5d39915..c7dd31b8bad7b 100644 --- a/lib/ui/window/pointer_data_packet_converter.h +++ b/lib/ui/window/pointer_data_packet_converter.h @@ -30,8 +30,13 @@ namespace flutter { struct PointerState { int64_t pointer_identifier; bool is_down; + bool is_flow_active; double physical_x; double physical_y; + double pan_x; + double pan_y; + double scale; + double angle; int64_t buttons; }; diff --git a/lib/ui/window/pointer_data_packet_converter_unittests.cc b/lib/ui/window/pointer_data_packet_converter_unittests.cc index deee0926738bf..304e6b6ff618a 100644 --- a/lib/ui/window/pointer_data_packet_converter_unittests.cc +++ b/lib/ui/window/pointer_data_packet_converter_unittests.cc @@ -86,6 +86,51 @@ void CreateSimulatedMousePointerData(PointerData& data, // NOLINT data.scroll_delta_y = scroll_delta_y; } +void CreateSimulatedTrackpadGestureData(PointerData& data, // NOLINT + PointerData::Change change, + int64_t device, + double dx, + double dy, + double pan_x, + double pan_y, + double scale, + double angle) { + data.time_stamp = 0; + data.change = change; + data.kind = PointerData::DeviceKind::kMouse; + data.signal_kind = PointerData::SignalKind::kNone; + data.device = device; + data.pointer_identifier = 0; + data.physical_x = dx; + data.physical_y = dy; + data.physical_delta_x = 0.0; + data.physical_delta_y = 0.0; + data.buttons = 0; + data.obscured = 0; + data.synthesized = 0; + data.pressure = 0.0; + data.pressure_min = 0.0; + data.pressure_max = 0.0; + data.distance = 0.0; + data.distance_max = 0.0; + data.size = 0.0; + data.radius_major = 0.0; + data.radius_minor = 0.0; + data.radius_min = 0.0; + data.radius_max = 0.0; + data.orientation = 0.0; + data.tilt = 0.0; + data.platformData = 0; + data.scroll_delta_x = 0.0; + data.scroll_delta_y = 0.0; + data.pan_x = pan_x; + data.pan_y = pan_y; + data.pan_delta_x = 0.0; + data.pan_delta_y = 0.0; + data.scale = scale; + data.angle = angle; +} + void UnpackPointerPacket(std::vector& output, // NOLINT std::unique_ptr packet) { size_t kBytesPerPointerData = kPointerDataFieldCount * kBytesPerField; @@ -599,5 +644,56 @@ TEST(PointerDataPacketConverterTest, CanConvetScroll) { ASSERT_EQ(result[6].scroll_delta_y, 0.0); } +TEST(PointerDataPacketConverterTest, CanConvertTrackpadGesture) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(3); + PointerData data; + CreateSimulatedTrackpadGestureData(data, PointerData::Change::kFlowStart, 0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedTrackpadGestureData(data, PointerData::Change::kFlowUpdate, 0, + 0.0, 0.0, 3.0, 4.0, 1.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedTrackpadGestureData(data, PointerData::Change::kFlowEnd, 0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); + packet->SetPointerData(2, data); + auto converted_packet = converter.Convert(std::move(packet)); + + std::vector result; + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)4); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].device, 0); + ASSERT_EQ(result[0].synthesized, 1); + + ASSERT_EQ(result[1].change, PointerData::Change::kFlowStart); + ASSERT_EQ(result[1].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[1].device, 0); + ASSERT_EQ(result[1].physical_x, 0.0); + ASSERT_EQ(result[1].physical_y, 0.0); + ASSERT_EQ(result[1].synthesized, 0); + + ASSERT_EQ(result[2].change, PointerData::Change::kFlowUpdate); + ASSERT_EQ(result[2].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[2].device, 0); + ASSERT_EQ(result[2].physical_x, 0.0); + ASSERT_EQ(result[2].physical_y, 0.0); + ASSERT_EQ(result[2].pan_x, 3.0); + ASSERT_EQ(result[2].pan_y, 4.0); + ASSERT_EQ(result[2].pan_delta_x, 3.0); + ASSERT_EQ(result[2].pan_delta_y, 4.0); + ASSERT_EQ(result[2].scale, 1.0); + ASSERT_EQ(result[2].angle, 0.0); + ASSERT_EQ(result[2].synthesized, 0); + + ASSERT_EQ(result[3].change, PointerData::Change::kFlowEnd); + ASSERT_EQ(result[3].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[3].device, 0); + ASSERT_EQ(result[3].physical_x, 0.0); + ASSERT_EQ(result[3].physical_y, 0.0); + ASSERT_EQ(result[3].synthesized, 0); +} + } // namespace testing } // namespace flutter diff --git a/lib/web_ui/lib/src/ui/pointer.dart b/lib/web_ui/lib/src/ui/pointer.dart index 230b0402ee44d..8befa3795b58e 100644 --- a/lib/web_ui/lib/src/ui/pointer.dart +++ b/lib/web_ui/lib/src/ui/pointer.dart @@ -12,6 +12,9 @@ enum PointerChange { down, move, up, + flowStart, + flowUpdate, + flowEnd, } enum PointerDeviceKind { @@ -59,6 +62,12 @@ class PointerData { this.platformData = 0, this.scrollDeltaX = 0.0, this.scrollDeltaY = 0.0, + this.panX = 0.0, + this.panY = 0.0, + this.panDeltaX = 0.0, + this.panDeltaY = 0.0, + this.scale = 0.0, + this.angle = 0.0, }); final int embedderId; final Duration timeStamp; @@ -89,6 +98,12 @@ class PointerData { final int platformData; final double scrollDeltaX; final double scrollDeltaY; + final double panX; + final double panY; + final double panDeltaX; + final double panDeltaY; + final double scale; + final double angle; @override String toString() => 'PointerData(x: $physicalX, y: $physicalY)'; @@ -121,7 +136,13 @@ class PointerData { 'tilt: $tilt, ' 'platformData: $platformData, ' 'scrollDeltaX: $scrollDeltaX, ' - 'scrollDeltaY: $scrollDeltaY' + 'scrollDeltaY: $scrollDeltaY, ' + 'panX: $panX, ' + 'panY: $panY, ' + 'panDeltaX: $panDeltaX, ' + 'panDeltaY: $panDeltaY, ' + 'scale: $scale, ' + 'angle: $angle' ')'; } } diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index 3a614b61963b3..876cdd3ca6823 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -6,9 +6,12 @@ import android.view.MotionEvent; import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import io.flutter.Log; import io.flutter.embedding.engine.renderer.FlutterRenderer; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; /** Sends touch information from Android to Flutter in a format that Flutter understands. */ public class AndroidTouchProcessor { @@ -21,7 +24,10 @@ public class AndroidTouchProcessor { PointerChange.HOVER, PointerChange.DOWN, PointerChange.MOVE, - PointerChange.UP + PointerChange.UP, + PointerChange.FLOW_START, + PointerChange.FLOW_UPDATE, + PointerChange.FLOW_END }) private @interface PointerChange { int CANCEL = 0; @@ -31,6 +37,9 @@ public class AndroidTouchProcessor { int DOWN = 4; int MOVE = 5; int UP = 6; + int FLOW_START = 7; + int FLOW_UPDATE = 8; + int FLOW_END = 9; } // Must match the PointerDeviceKind enum in pointer.dart. @@ -58,7 +67,7 @@ public class AndroidTouchProcessor { } // Must match the unpacking code in hooks.dart. - private static final int POINTER_DATA_FIELD_COUNT = 29; + private static final int POINTER_DATA_FIELD_COUNT = 35; private static final int BYTES_PER_FIELD = 8; // This value must match the value in framework's platform_view.dart. @@ -74,6 +83,8 @@ public class AndroidTouchProcessor { private final boolean trackMotionEvents; + private final Map ongoingPans = new HashMap<>(); + /** * Constructs an {@code AndroidTouchProcessor} that will send touch event data to the Flutter * execution context represented by the given {@link FlutterRenderer}. @@ -212,6 +223,24 @@ private void addPointerForIndex( } int pointerKind = getPointerDeviceTypeForToolType(event.getToolType(pointerIndex)); + // We use this in lieu of using event.getRawX and event.getRawY as we wish to support + // earlier versions than API level 29. + float viewToScreenCoords[] = {event.getX(pointerIndex), event.getY(pointerIndex)}; + transformMatrix.mapPoints(viewToScreenCoords); + long buttons; + if (pointerKind == PointerDeviceKind.MOUSE) { + buttons = event.getButtonState() & 0x1F; + if (buttons == 0 + && event.getSource() == InputDevice.SOURCE_MOUSE + && pointerChange == PointerChange.DOWN) { + ongoingPans.put(event.getPointerId(pointerIndex), viewToScreenCoords); + } + } else if (pointerKind == PointerDeviceKind.STYLUS) { + buttons = (event.getButtonState() >> 4) & 0xF; + } else { + buttons = 0; + } + // Log.e("AndroidTouchProcessor", "actionMasked: " + event.getActionMasked()); int signalKind = event.getActionMasked() == MotionEvent.ACTION_SCROLL @@ -222,39 +251,32 @@ private void addPointerForIndex( packet.putLong(motionEventId); // motionEventId packet.putLong(timeStamp); // time_stamp - packet.putLong(pointerChange); // change - packet.putLong(pointerKind); // kind + if (ongoingPans.containsKey(event.getPointerId(pointerIndex))) { + packet.putLong(getPointerChangeForGesture(pointerChange)); // change + packet.putLong(PointerDeviceKind.TOUCH); // kind + } else { + packet.putLong(pointerChange); // change + packet.putLong(pointerKind); // kind + } packet.putLong(signalKind); // signal_kind packet.putLong(event.getPointerId(pointerIndex)); // device + // Log.e("AndroidTouchProcessor", "device: " + event.getPointerId(pointerIndex)); packet.putLong(0); // pointer_identifier, will be generated in pointer_data_packet_converter.cc. - // We use this in lieu of using event.getRawX and event.getRawY as we wish to support - // earlier versions than API level 29. - float viewToScreenCoords[] = {event.getX(pointerIndex), event.getY(pointerIndex)}; - transformMatrix.mapPoints(viewToScreenCoords); - packet.putDouble(viewToScreenCoords[0]); // physical_x - packet.putDouble(viewToScreenCoords[1]); // physical_y + if (ongoingPans.containsKey(event.getPointerId(pointerIndex))) { + float[] panStart = ongoingPans.get(event.getPointerId(pointerIndex)); + packet.putDouble(panStart[0]); + packet.putDouble(panStart[1]); + } else { + packet.putDouble(viewToScreenCoords[0]); // physical_x + packet.putDouble(viewToScreenCoords[1]); // physical_y + } packet.putDouble( 0.0); // physical_delta_x, will be generated in pointer_data_packet_converter.cc. packet.putDouble( 0.0); // physical_delta_y, will be generated in pointer_data_packet_converter.cc. - long buttons; - if (pointerKind == PointerDeviceKind.MOUSE) { - buttons = event.getButtonState() & 0x1F; - // TODO(dkwingsmt): Remove this fix after implementing touchpad gestures - // https://github.com/flutter/flutter/issues/23604#issuecomment-524471152 - if (buttons == 0 - && event.getSource() == InputDevice.SOURCE_MOUSE - && (pointerChange == PointerChange.DOWN || pointerChange == PointerChange.MOVE)) { - buttons = _POINTER_BUTTON_PRIMARY; - } - } else if (pointerKind == PointerDeviceKind.STYLUS) { - buttons = (event.getButtonState() >> 4) & 0xF; - } else { - buttons = 0; - } packet.putLong(buttons); // buttons packet.putLong(0); // obscured @@ -308,6 +330,24 @@ private void addPointerForIndex( packet.putDouble(0.0); // scroll_delta_x packet.putDouble(0.0); // scroll_delta_x } + + if (ongoingPans.containsKey(event.getPointerId(pointerIndex))) { + float[] panStart = ongoingPans.get(event.getPointerId(pointerIndex)); + packet.putDouble(viewToScreenCoords[0] - panStart[0]); + packet.putDouble(viewToScreenCoords[1] - panStart[1]); + } else { + packet.putDouble(0.0); // pan_x + packet.putDouble(0.0); // pan_y + } + packet.putDouble(0.0); // pan_delta_x + packet.putDouble(0.0); // pan_delta_y + packet.putDouble(1.0); // scale + packet.putDouble(0.0); // angle + + if (ongoingPans.containsKey(event.getPointerId(pointerIndex)) + && getPointerChangeForGesture(pointerChange) == PointerChange.FLOW_END) { + ongoingPans.remove(event.getPointerId(pointerIndex)); + } } @PointerChange @@ -342,6 +382,19 @@ private int getPointerChangeForAction(int maskedAction) { return -1; } + @PointerChange + private int getPointerChangeForGesture(int pointerChange) { + if (pointerChange == PointerChange.DOWN) { + return PointerChange.FLOW_START; + } else if (pointerChange == PointerChange.MOVE) { + return PointerChange.FLOW_UPDATE; + } else if (pointerChange == PointerChange.UP || pointerChange == PointerChange.CANCEL) { + return PointerChange.FLOW_END; + } + Log.e("AndroidTouchProcessor", "Unexpected pointerChangeForGesture: " + pointerChange); + return -1; + } + @PointerDeviceKind private int getPointerDeviceTypeForToolType(int toolType) { switch (toolType) { diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h b/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h index 7a5219fb71927..d822ca7d8981d 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h @@ -49,8 +49,10 @@ extern NSNotificationName const FlutterSemanticsUpdateNotification; */ FLUTTER_DARWIN_EXPORT #ifdef __IPHONE_13_4 -@interface FlutterViewController - : UIViewController +@interface FlutterViewController : UIViewController #else @interface FlutterViewController : UIViewController #endif diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 5666f336af88b..186bb6ba41081 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -113,6 +113,9 @@ @implementation FlutterViewController { fml::scoped_nsobject _scrollView; fml::scoped_nsobject _pointerInteraction API_AVAILABLE(ios(13.4)); fml::scoped_nsobject _panGestureRecognizer API_AVAILABLE(ios(13.4)); + fml::scoped_nsobject _pinchGestureRecognizer API_AVAILABLE(ios(13.4)); + fml::scoped_nsobject _rotationGestureRecognizer + API_AVAILABLE(ios(13.4)); MouseState _mouseState; } @@ -650,10 +653,21 @@ - (void)viewDidLoad { [self.view addInteraction:_pointerInteraction]; _panGestureRecognizer.reset( - [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(scrollEvent:)]); + [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panEvent:)]); _panGestureRecognizer.get().allowedScrollTypesMask = UIScrollTypeMaskAll; _panGestureRecognizer.get().allowedTouchTypes = @[ @(UITouchTypeIndirectPointer) ]; + _panGestureRecognizer.get().delegate = self; [_flutterView.get() addGestureRecognizer:_panGestureRecognizer.get()]; + _pinchGestureRecognizer.reset( + [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)]); + _pinchGestureRecognizer.get().allowedTouchTypes = @[ @(UITouchTypeIndirectPointer) ]; + _pinchGestureRecognizer.get().delegate = self; + [_flutterView.get() addGestureRecognizer:_pinchGestureRecognizer.get()]; + _rotationGestureRecognizer.reset( + [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotationEvent:)]); + _rotationGestureRecognizer.get().allowedTouchTypes = @[ @(UITouchTypeIndirectPointer) ]; + _rotationGestureRecognizer.get().delegate = self; + [_flutterView.get() addGestureRecognizer:_rotationGestureRecognizer.get()]; } [super viewDidLoad]; @@ -915,6 +929,11 @@ - (void)dispatchTouches:(NSSet*)touches case flutter::PointerData::Change::kRemove: // We don't use kAdd/kRemove. break; + case flutter::PointerData::Change::kFlowStart: + case flutter::PointerData::Change::kFlowUpdate: + case flutter::PointerData::Change::kFlowEnd: + // We don't send flow events here + break; } // pressure_min is always 0.0 @@ -1564,7 +1583,8 @@ - (BOOL)isPresentingViewController { pointer_data.kind = flutter::PointerData::DeviceKind::kMouse; pointer_data.change = _mouseState.flutter_state_is_added ? flutter::PointerData::Change::kAdd : flutter::PointerData::Change::kHover; - pointer_data.pointer_identifier = reinterpret_cast(_pointerInteraction.get()); + pointer_data.time_stamp = [[NSProcessInfo processInfo] systemUptime] * kMicrosecondsPerSecond; + pointer_data.device = reinterpret_cast(_pointerInteraction.get()); pointer_data.physical_x = _mouseState.location.x; pointer_data.physical_y = _mouseState.location.y; @@ -1590,28 +1610,55 @@ - (UIPointerRegion*)pointerInteraction:(UIPointerInteraction*)interaction return nil; } -- (void)scrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) { +- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer + shouldRecognizeSimultaneouslyWithGestureRecognizer: + (UIGestureRecognizer*)otherGestureRecognizer { + return YES; +} + +- (void)panEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) { CGPoint translation = [recognizer translationInView:self.view]; const CGFloat scale = [UIScreen mainScreen].scale; translation.x *= scale; translation.y *= scale; - auto packet = std::make_unique(1); - flutter::PointerData pointer_data = [self generatePointerDataForMouse]; - pointer_data.signal_kind = flutter::PointerData::SignalKind::kScroll; - pointer_data.scroll_delta_x = (translation.x - _mouseState.last_translation.x); - pointer_data.scroll_delta_y = -(translation.y - _mouseState.last_translation.y); - - // The translation reported by UIPanGestureRecognizer is the total translation - // generated by the pan gesture since the gesture began. We need to be able - // to keep track of the last translation value in order to generate the deltaX - // and deltaY coordinates for each subsequent scroll event. - if (recognizer.state != UIGestureRecognizerStateEnded) { + pointer_data.kind = flutter::PointerData::DeviceKind::kTouch; + if (recognizer.state == UIGestureRecognizerStateBegan) { + pointer_data.change = flutter::PointerData::Change::kFlowStart; + } else if (recognizer.state == UIGestureRecognizerStateChanged) { + pointer_data.change = flutter::PointerData::Change::kFlowUpdate; + pointer_data.pan_x = translation.x; + pointer_data.pan_y = translation.y; + pointer_data.pan_delta_x = 0; // Delta will be generated in pointer_data_packet_converter.cc. + pointer_data.pan_delta_y = 0; // Delta will be generated in pointer_data_packet_converter.cc. + pointer_data.scale = 1; _mouseState.last_translation = translation; } else { _mouseState.last_translation = CGPointZero; + pointer_data.change = flutter::PointerData::Change::kFlowEnd; + } + + auto packet = std::make_unique(1); + packet->SetPointerData(/*index=*/0, pointer_data); + + [_engine.get() dispatchPointerDataPacket:std::move(packet)]; +} + +- (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) { + auto packet = std::make_unique(1); + + flutter::PointerData pointer_data = [self generatePointerDataForMouse]; + pointer_data.kind = flutter::PointerData::DeviceKind::kTouch; + if (recognizer.state == UIGestureRecognizerStateBegan) { + pointer_data.change = flutter::PointerData::Change::kFlowStart; + } else if (recognizer.state == UIGestureRecognizerStateChanged) { + pointer_data.change = flutter::PointerData::Change::kFlowUpdate; + pointer_data.scale = recognizer.scale; + pointer_data.angle = _rotationGestureRecognizer.get().rotation; + } else { + pointer_data.change = flutter::PointerData::Change::kFlowEnd; } packet->SetPointerData(/*index=*/0, pointer_data); @@ -1619,6 +1666,10 @@ - (void)scrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) [_engine.get() dispatchPointerDataPacket:std::move(packet)]; } +- (void)rotationEvent:(UIRotationGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) { + // Do nothing (I think it's required to have this callback tho?) +} + #pragma mark - State Restoration - (void)encodeRestorableStateWithCoder:(NSCoder*)coder { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index ab9d8833bf43b..40190afe9d533 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -63,6 +63,47 @@ */ int64_t buttons = 0; + /** + * Pan gesture is currently sending us events. + */ + bool pan_gesture_active = false; + + /** + * The accumulated gesture pan + */ + CGFloat deltaX = 0; + CGFloat deltaY = 0; + + /** + * Scale gesture is currently sending us events. + */ + bool scale_gesture_active = false; + + /** + * The accumulated gesture zoom scale + */ + CGFloat scale = 0; + + /** + * Rotate gesture is currently sending use events. + */ + bool rotate_gesture_active = false; + + /** + * The accumulated gesture rotation + */ + CGFloat angle = 0; + + /** + * Resets all gesture state to default values. + */ + void GestureReset() { + deltaX = 0; + deltaY = 0; + scale = 0; + angle = 0; + } + /** * Resets all state to default values. */ @@ -71,6 +112,7 @@ void Reset() { flutter_state_is_down = false; has_pending_exit = false; buttons = 0; + GestureReset(); } }; @@ -145,6 +187,11 @@ - (void)addInternalPlugins; */ - (void)dispatchMouseEvent:(nonnull NSEvent*)event; +/** + * Calls dispatchMouseEvent:phase: with a phase determined by event.phase. + */ +- (void)dispatchGestureEvent:(nonnull NSEvent*)event; + /** * Converts |event| to a FlutterPointerEvent with the given phase, and sends it to the engine. */ @@ -471,6 +518,18 @@ - (void)dispatchMouseEvent:(nonnull NSEvent*)event { [self dispatchMouseEvent:event phase:phase]; } +- (void)dispatchGestureEvent:(nonnull NSEvent*)event { + if (event.phase == NSEventPhaseBegan || event.phase == NSEventPhaseMayBegin) { + [self dispatchMouseEvent:event phase:kFlowStart]; + } else if (event.phase == NSEventPhaseChanged) { + [self dispatchMouseEvent:event phase:kFlowUpdate]; + } else if (event.phase == NSEventPhaseEnded || event.phase == NSEventPhaseCancelled) { + [self dispatchMouseEvent:event phase:kFlowEnd]; + } else if (event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone) { + [self dispatchMouseEvent:event phase:kHover]; + } +} + - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase { NSAssert(self.viewLoaded, @"View must be loaded before it handles the mouse event"); // There are edge cases where the system will deliver enter out of order relative to other @@ -480,6 +539,36 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase { return; } + // Multiple gesture recognizers could be active at once, we can't send multiple kFlowStart + // For example, rotation and magnification + if (phase == kFlowStart) { + bool gestureAlreadyDown = _mouseState.pan_gesture_active || _mouseState.scale_gesture_active || + _mouseState.rotate_gesture_active; + if (event.type == NSEventTypeScrollWheel) { + _mouseState.pan_gesture_active = true; + } else if (event.type == NSEventTypeMagnify) { + _mouseState.scale_gesture_active = true; + } else if (event.type == NSEventTypeRotate) { + _mouseState.rotate_gesture_active = true; + } + if (gestureAlreadyDown) { + return; + } + } + if (phase == kFlowEnd) { + if (event.type == NSEventTypeScrollWheel) { + _mouseState.pan_gesture_active = false; + } else if (event.type == NSEventTypeMagnify) { + _mouseState.scale_gesture_active = false; + } else if (event.type == NSEventTypeRotate) { + _mouseState.rotate_gesture_active = false; + } + if (_mouseState.pan_gesture_active || _mouseState.scale_gesture_active || + _mouseState.rotate_gesture_active) { + return; + } + } + // If a pointer added event hasn't been sent, synthesize one using this event for the basic // information. if (!_mouseState.flutter_state_is_added && phase != kAdd) { @@ -509,7 +598,29 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase { .buttons = phase == kAdd ? 0 : _mouseState.buttons, }; - if (event.type == NSEventTypeScrollWheel) { + if (phase == kFlowStart) { + flutterEvent.device_kind = kFlutterPointerDeviceKindTouch; + } else if (phase == kFlowUpdate) { + flutterEvent.device_kind = kFlutterPointerDeviceKindTouch; + if (event.type == NSEventTypeScrollWheel) { + //_mouseState.deltaX += event.deltaX; + //_mouseState.deltaY += event.deltaY; + _mouseState.deltaX += event.scrollingDeltaX; + _mouseState.deltaY += event.scrollingDeltaY; + } else if (event.type == NSEventTypeMagnify) { + _mouseState.scale += event.magnification; + } else if (event.type == NSEventTypeRotate) { + _mouseState.angle += event.rotation * (M_PI / 180.0); + } + flutterEvent.pan_x = _mouseState.deltaX; + flutterEvent.pan_y = _mouseState.deltaY; + // Scale gesture value should be in range 0->infinity + flutterEvent.scale = pow(2.0, _mouseState.scale); + flutterEvent.angle = _mouseState.angle; + } else if (phase == kFlowEnd) { + flutterEvent.device_kind = kFlutterPointerDeviceKindTouch; + _mouseState.GestureReset(); + } else if (event.type == NSEventTypeScrollWheel) { flutterEvent.signal_kind = kFlutterPointerSignalKindScroll; double pixelsPerLine = 1.0; @@ -752,9 +863,19 @@ - (void)mouseMoved:(NSEvent*)event { } - (void)scrollWheel:(NSEvent*)event { - // TODO: Add gesture-based (trackpad) scroll support once it's supported by the engine rather - // than always using kHover. - [self dispatchMouseEvent:event phase:kHover]; + [self dispatchGestureEvent:event]; +} + +- (void)magnifyWithEvent:(NSEvent*)event { + [self dispatchGestureEvent:event]; +} + +- (void)rotateWithEvent:(NSEvent*)event { + [self dispatchGestureEvent:event]; +} + +- (void)swipeWithEvent:(NSEvent*)event { + // Not needed, it's handled by scrollWheel } @end diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index dfe40017dfb41..757a95cc81255 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -1455,6 +1455,12 @@ inline flutter::PointerData::Change ToPointerDataChange( return flutter::PointerData::Change::kRemove; case kHover: return flutter::PointerData::Change::kHover; + case kFlowStart: + return flutter::PointerData::Change::kFlowStart; + case kFlowUpdate: + return flutter::PointerData::Change::kFlowUpdate; + case kFlowEnd: + return flutter::PointerData::Change::kFlowEnd; } return flutter::PointerData::Change::kCancel; } @@ -1502,6 +1508,9 @@ inline int64_t PointerDataButtonsForLegacyEvent( case flutter::PointerData::Change::kRemove: case flutter::PointerData::Change::kHover: case flutter::PointerData::Change::kUp: + case flutter::PointerData::Change::kFlowStart: + case flutter::PointerData::Change::kFlowUpdate: + case flutter::PointerData::Change::kFlowEnd: return 0; } return 0; @@ -1567,6 +1576,13 @@ FlutterEngineResult FlutterEngineSendPointerEvent( pointer_data.buttons = SAFE_ACCESS(current, buttons, 0); } } + pointer_data.pan_x = SAFE_ACCESS(current, pan_x, 0.0); + pointer_data.pan_y = SAFE_ACCESS(current, pan_y, 0.0); + // Delta will be generated in pointer_data_packet_converter.cc. + pointer_data.pan_delta_x = 0.0; + pointer_data.pan_delta_y = 0.0; + pointer_data.scale = SAFE_ACCESS(current, scale, 0.0); + pointer_data.angle = SAFE_ACCESS(current, angle, 0.0); packet->SetPointerData(i, pointer_data); current = reinterpret_cast( reinterpret_cast(current) + current->struct_size); diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 473724144ae16..7b1b592ee2828 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -617,6 +617,12 @@ typedef enum { kRemove, /// The pointer moved while up. kHover, + /// A gesture started on this pointer + kFlowStart, + /// The gesture updated + kFlowUpdate, + /// The gesture ended + kFlowEnd, } FlutterPointerPhase; /// The device type that created a pointer event. @@ -672,6 +678,14 @@ typedef struct { FlutterPointerDeviceKind device_kind; /// The buttons currently pressed, if any. int64_t buttons; + /// The x offset of the gesture in physical pixels. + double pan_x; + /// The y offset of the gesture in physical pixels. + double pan_y; + /// The scale of the gesture, where 1.0 is the initial scale. + double scale; + /// The angle of the gesture in radians, where 0.0 is the initial angle. + double angle; } FlutterPointerEvent; typedef enum { diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index 0033a3195dd17..8a4199b9e70de 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -711,6 +711,37 @@ void fl_engine_send_mouse_pointer_event(FlEngine* self, self->embedder_api.SendPointerEvent(self->engine, &fl_event, 1); } +void fl_engine_send_pointer_flow_event(FlEngine* self, + int64_t device, + size_t timestamp, + double x, + double y, + FlutterPointerPhase phase, + double pan_x, + double pan_y, + double scale, + double angle) { + g_return_if_fail(FL_IS_ENGINE(self)); + + if (self->engine == nullptr) { + return; + } + + FlutterPointerEvent fl_event = {}; + fl_event.struct_size = sizeof(fl_event); + fl_event.timestamp = timestamp; + fl_event.x = x; + fl_event.y = y; + fl_event.phase = phase; + fl_event.pan_x = pan_x; + fl_event.pan_y = pan_y; + fl_event.scale = scale; + fl_event.angle = angle; + fl_event.device = device; + fl_event.device_kind = kFlutterPointerDeviceKindTouch; + self->embedder_api.SendPointerEvent(self->engine, &fl_event, 1); +} + void fl_engine_send_key_event(FlEngine* self, const FlutterKeyEvent* event, FlutterKeyEventCallback callback, diff --git a/shell/platform/linux/fl_engine_private.h b/shell/platform/linux/fl_engine_private.h index a576a64e9608c..560e75ff9d313 100644 --- a/shell/platform/linux/fl_engine_private.h +++ b/shell/platform/linux/fl_engine_private.h @@ -189,6 +189,17 @@ void fl_engine_send_mouse_pointer_event(FlEngine* engine, double scroll_delta_y, int64_t buttons); +void fl_engine_send_pointer_flow_event(FlEngine* self, + int64_t device, + size_t timestamp, + double x, + double y, + FlutterPointerPhase phase, + double pan_x, + double pan_y, + double scale, + double angle); + /** * fl_engine_send_key_event: */ diff --git a/shell/platform/linux/fl_engine_test.cc b/shell/platform/linux/fl_engine_test.cc index 082f936ee8fe7..1cc82a2bdeac9 100644 --- a/shell/platform/linux/fl_engine_test.cc +++ b/shell/platform/linux/fl_engine_test.cc @@ -71,6 +71,43 @@ TEST(FlEngineTest, MousePointer) { EXPECT_TRUE(called); } +// Checks sending trackpad gesture events works. +TEST(FlEngineTest, PointerFlow) { + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + + bool called = false; + embedder_api->SendPointerEvent = MOCK_ENGINE_PROC( + SendPointerEvent, + ([&called](auto engine, const FlutterPointerEvent* events, + size_t events_count) { + called = true; + EXPECT_EQ(events_count, static_cast(1)); + EXPECT_EQ(events[0].phase, kFlowUpdate); + EXPECT_EQ(events[0].timestamp, static_cast(1234567890)); + EXPECT_EQ(events[0].x, 800); + EXPECT_EQ(events[0].y, 600); + EXPECT_EQ(events[0].device, static_cast(0)); + EXPECT_EQ(events[0].signal_kind, kFlutterPointerSignalKindNone); + EXPECT_EQ(events[0].pan_x, 1.5); + EXPECT_EQ(events[0].pan_y, 2.5); + EXPECT_EQ(events[0].scale, 3.5); + EXPECT_EQ(events[0].angle, 4.5); + EXPECT_EQ(events[0].device_kind, kFlutterPointerDeviceKindTouch); + EXPECT_EQ(events[0].buttons, 0); + + return kSuccess; + })); + + g_autoptr(GError) error = nullptr; + EXPECT_TRUE(fl_engine_start(engine, &error)); + EXPECT_EQ(error, nullptr); + fl_engine_send_pointer_flow_event(engine, 0, 1234567890, 800, 600, + kFlowUpdate, 1.5, 2.5, 3.5, 4.5); + + EXPECT_TRUE(called); +} + // Checks dispatching a semantics action works. TEST(FlEngineTest, DispatchSemanticsAction) { g_autoptr(FlEngine) engine = make_mock_engine(); diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 1fbdd633348ef..d839864d6edad 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -39,6 +39,17 @@ struct _FlView { // Pointer button state recorded for sending status updates. int64_t button_state; + gdouble last_x; + gdouble last_y; + + gboolean pan_started; + gdouble pan_x; + gdouble pan_y; + + gboolean zoom_rotate_started; + gdouble scale; + gdouble angle; + // Flutter system channel handlers. FlAccessibilityPlugin* accessibility_plugin; FlKeyboardManager* keyboard_manager; @@ -157,6 +168,8 @@ static gboolean fl_view_send_pointer_button_event(FlView* self, } gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self)); + self->last_x = event->x * scale_factor; + self->last_y = event->y * scale_factor; fl_engine_send_mouse_pointer_event( self->engine, phase, event->time * kMicrosecondsPerMillisecond, event->x * scale_factor, event->y * scale_factor, 0, 0, @@ -218,6 +231,31 @@ static gboolean event_box_leave_notify_event(GtkWidget* widget, GdkEventCrossing* event, FlView* view); +static void event_box_gesture_rotation_begin(GtkGestureRotate* gesture, + GdkEventSequence* sequence, + FlView* view); + +static void event_box_gesture_rotation_update(GtkGestureRotate* widget, + gdouble angle, + gdouble delta, + FlView* view); + +static void event_box_gesture_rotation_end(GtkGestureRotate* gesture, + GdkEventSequence* sequence, + FlView* view); + +static void event_box_gesture_zoom_begin(GtkGestureZoom* gesture, + GdkEventSequence* sequence, + FlView* view); + +static void event_box_gesture_zoom_update(GtkGestureZoom* widget, + gdouble scale, + FlView* view); + +static void event_box_gesture_zoom_end(GtkGestureZoom* gesture, + GdkEventSequence* sequence, + FlView* view); + static void fl_view_constructed(GObject* object) { FlView* self = FL_VIEW(object); @@ -255,6 +293,19 @@ static void fl_view_constructed(GObject* object) { G_CALLBACK(event_box_enter_notify_event), self); g_signal_connect(self->event_box, "leave-notify-event", G_CALLBACK(event_box_leave_notify_event), self); + GtkGesture* zoom = gtk_gesture_zoom_new(self->event_box); + g_signal_connect(zoom, "begin", G_CALLBACK(event_box_gesture_zoom_begin), + self); + g_signal_connect(zoom, "scale-changed", + G_CALLBACK(event_box_gesture_zoom_update), self); + g_signal_connect(zoom, "end", G_CALLBACK(event_box_gesture_zoom_end), self); + GtkGesture* rotate = gtk_gesture_rotate_new(self->event_box); + g_signal_connect(rotate, "begin", + G_CALLBACK(event_box_gesture_rotation_begin), self); + g_signal_connect(rotate, "angle-changed", + G_CALLBACK(event_box_gesture_rotation_update), self); + g_signal_connect(rotate, "end", G_CALLBACK(event_box_gesture_rotation_end), + self); } static void fl_view_set_property(GObject* object, @@ -518,18 +569,50 @@ static gboolean event_box_scroll_event(GtkWidget* widget, scroll_delta_x = 1; } + gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(view)); + // The multiplier is taken from the Chromium source // (ui/events/x/events_x_utils.cc). const int kScrollOffsetMultiplier = 53; - scroll_delta_x *= kScrollOffsetMultiplier; - scroll_delta_y *= kScrollOffsetMultiplier; - - gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(view)); - fl_engine_send_mouse_pointer_event( - view->engine, view->button_state != 0 ? kMove : kHover, - event->time * kMicrosecondsPerMillisecond, event->x * scale_factor, - event->y * scale_factor, scroll_delta_x, scroll_delta_y, - view->button_state); + scroll_delta_x *= kScrollOffsetMultiplier * scale_factor; + scroll_delta_y *= kScrollOffsetMultiplier * scale_factor; + + if (gdk_device_get_source(gdk_event_get_source_device((GdkEvent*)event)) == + GDK_SOURCE_TOUCHPAD) { + scroll_delta_x *= -1; + scroll_delta_y *= -1; + if (event->is_stop) { + fl_engine_send_pointer_flow_event( + view->engine, 123123, event->time * kMicrosecondsPerMillisecond, + event->x * scale_factor, event->y * scale_factor, kFlowEnd, + view->pan_x, view->pan_y, 0, 0); + view->pan_started = FALSE; + } else { + if (!view->pan_started) { + view->pan_x = 0; + view->pan_y = 0; + fl_engine_send_pointer_flow_event( + view->engine, 123123, event->time * kMicrosecondsPerMillisecond, + event->x * scale_factor, event->y * scale_factor, kFlowStart, 0, 0, + 0, 0); + view->pan_started = TRUE; + } + view->pan_x += scroll_delta_x; + view->pan_y += scroll_delta_y; + fl_engine_send_pointer_flow_event( + view->engine, 123123, event->time * kMicrosecondsPerMillisecond, + event->x * scale_factor, event->y * scale_factor, kFlowUpdate, + view->pan_x, view->pan_y, 1, 0); + } + } else { + view->last_x = event->x * scale_factor; + view->last_y = event->y * scale_factor; + fl_engine_send_mouse_pointer_event( + view->engine, view->button_state != 0 ? kMove : kHover, + event->time * kMicrosecondsPerMillisecond, event->x * scale_factor, + event->y * scale_factor, scroll_delta_x, scroll_delta_y, + view->button_state); + } return TRUE; } @@ -541,7 +624,8 @@ static void check_pointer_inside(FlView* view, GdkEvent* event) { gdouble x, y; if (gdk_event_get_coords(event, &x, &y)) { gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(view)); - + view->last_x = x * scale_factor; + view->last_y = y * scale_factor; fl_engine_send_mouse_pointer_event( view->engine, kAdd, gdk_event_get_time(event) * kMicrosecondsPerMillisecond, @@ -560,6 +644,8 @@ static gboolean event_box_motion_notify_event(GtkWidget* widget, check_pointer_inside(view, reinterpret_cast(event)); gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(view)); + view->last_x = event->x * scale_factor; + view->last_y = event->y * scale_factor; fl_engine_send_mouse_pointer_event( view->engine, view->button_state != 0 ? kMove : kHover, event->time * kMicrosecondsPerMillisecond, event->x * scale_factor, @@ -592,6 +678,8 @@ static gboolean event_box_leave_notify_event(GtkWidget* widget, // release. if (view->pointer_inside && view->button_state == 0) { gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(view)); + view->last_x = event->x * scale_factor; + view->last_y = event->y * scale_factor; fl_engine_send_mouse_pointer_event( view->engine, kRemove, event->time * kMicrosecondsPerMillisecond, event->x * scale_factor, event->y * scale_factor, 0, 0, @@ -602,6 +690,73 @@ static gboolean event_box_leave_notify_event(GtkWidget* widget, return TRUE; } +static void event_box_gesture_rotation_begin(GtkGestureRotate* gesture, + GdkEventSequence* sequence, + FlView* view) { + if (!view->zoom_rotate_started) { + view->zoom_rotate_started = true; + view->scale = 1; + view->angle = 0; + fl_engine_send_pointer_flow_event(view->engine, 123123, g_get_real_time(), + view->last_x, view->last_y, kFlowStart, 0, + 0, 0, 0); + } +} + +static void event_box_gesture_rotation_update(GtkGestureRotate* widget, + gdouble angle, + gdouble delta, + FlView* view) { + view->angle = angle; + fl_engine_send_pointer_flow_event(view->engine, 123123, g_get_real_time(), + view->last_x, view->last_y, kFlowUpdate, 0, + 0, view->scale, view->angle); +} + +static void event_box_gesture_rotation_end(GtkGestureRotate* gesture, + GdkEventSequence* sequence, + FlView* view) { + if (view->zoom_rotate_started) { + view->zoom_rotate_started = false; + fl_engine_send_pointer_flow_event(view->engine, 123123, g_get_real_time(), + view->last_x, view->last_y, kFlowEnd, 0, + 0, 0, 0); + } +} + +static void event_box_gesture_zoom_begin(GtkGestureZoom* gesture, + GdkEventSequence* sequence, + FlView* view) { + if (!view->zoom_rotate_started) { + view->zoom_rotate_started = true; + view->scale = 1; + view->angle = 0; + fl_engine_send_pointer_flow_event(view->engine, 123123, g_get_real_time(), + view->last_x, view->last_y, kFlowStart, 0, + 0, 0, 0); + } +} + +static void event_box_gesture_zoom_update(GtkGestureZoom* widget, + gdouble scale, + FlView* view) { + view->scale = scale; + fl_engine_send_pointer_flow_event(view->engine, 123123, g_get_real_time(), + view->last_x, view->last_y, kFlowUpdate, 0, + 0, view->scale, view->angle); +} + +static void event_box_gesture_zoom_end(GtkGestureZoom* gesture, + GdkEventSequence* sequence, + FlView* view) { + if (view->zoom_rotate_started) { + view->zoom_rotate_started = false; + fl_engine_send_pointer_flow_event(view->engine, 123123, g_get_real_time(), + view->last_x, view->last_y, kFlowEnd, 0, + 0, 0, 0); + } +} + // Implements GtkWidget::key_press_event. static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) { FlView* self = FL_VIEW(widget); diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 525e852577b6b..3d5bf7ffd5ef8 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -105,6 +105,8 @@ source_set("flutter_windows_source") { ] } else { sources += [ + "direct_manipulation.cc", + "direct_manipulation.h", "dpi_utils_win32.cc", "dpi_utils_win32.h", "flutter_window_win32.cc", @@ -143,19 +145,16 @@ source_set("flutter_windows_source") { public_configs = [ ":relative_angle_headers" ] - if (target_os == "winuwp") { - defines = [ - "_SILENCE_CLANG_COROUTINE_MESSAGE", - "FLUTTER_ENGINE_NO_PROTOTYPES", - ] - } else { - defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ] - } + defines = [ + "_SILENCE_CLANG_COROUTINE_MESSAGE", + "FLUTTER_ENGINE_NO_PROTOTYPES", + ] public_deps = [ ":string_conversion" ] deps = [ ":flutter_windows_headers", + "//flutter/fml:fml", "//flutter/shell/platform/common:common_cpp", "//flutter/shell/platform/common:common_cpp_input", "//flutter/shell/platform/common:common_cpp_switches", @@ -238,6 +237,7 @@ executable("flutter_windows_unittests") { } else { sources += [ # TODO move first two tests to common once above TODO's unblocked. + "direct_manipulation_unittests.cc", "dpi_utils_win32_unittests.cc", "flutter_project_bundle_unittests.cc", "flutter_window_win32_unittests.cc", @@ -268,6 +268,8 @@ executable("flutter_windows_unittests") { public_configs = [ "//flutter:config" ] + defines = [ "_SILENCE_CLANG_COROUTINE_MESSAGE" ] + deps = [ ":flutter_windows_fixtures", ":flutter_windows_headers", diff --git a/shell/platform/windows/direct_manipulation.cc b/shell/platform/windows/direct_manipulation.cc new file mode 100644 index 0000000000000..0d4e93a5dafe7 --- /dev/null +++ b/shell/platform/windows/direct_manipulation.cc @@ -0,0 +1,310 @@ +// 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/fml/logging.h" + +#include "flutter/shell/platform/windows/direct_manipulation.h" +#include "flutter/shell/platform/windows/window_binding_handler_delegate.h" +#include "flutter/shell/platform/windows/window_win32.h" + +namespace flutter { + +STDMETHODIMP DirectManipulationEventHandler::QueryInterface(REFIID iid, + void** ppv) { + if ((iid == IID_IUnknown) || + (iid == IID_IDirectManipulationViewportEventHandler)) { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } else if (iid == IID_IDirectManipulationInteractionEventHandler) { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + return E_NOINTERFACE; +} + +HRESULT DirectManipulationEventHandler::OnViewportStatusChanged( + IDirectManipulationViewport* viewport, + DIRECTMANIPULATION_STATUS current, + DIRECTMANIPULATION_STATUS previous) { + if (current == DIRECTMANIPULATION_RUNNING) { + if (!resetting_) { + if (owner_->binding_handler_delegate) { + owner_->binding_handler_delegate->OnPointerFlowStart( + reinterpret_cast(this)); + } + } + } else if (previous == DIRECTMANIPULATION_RUNNING) { + if (resetting_) { + resetting_ = false; + } else { + if (owner_->binding_handler_delegate) { + owner_->binding_handler_delegate->OnPointerFlowEnd( + reinterpret_cast(this)); + } + // Need to reset the content transform + // Use resetting_ flag to prevent sending reset also to the framework + resetting_ = true; + RECT rect; + HRESULT hr = viewport->GetViewportRect(&rect); + if (FAILED(hr)) { + FML_LOG(ERROR) << "Failed to get the current viewport rect"; + return E_FAIL; + } + hr = viewport->ZoomToRect(rect.left, rect.top, rect.right, rect.bottom, + false); + if (FAILED(hr)) { + FML_LOG(ERROR) << "Failed to reset the gesture using ZoomToRect"; + return E_FAIL; + } + } + } + return S_OK; +} + +HRESULT DirectManipulationEventHandler::OnViewportUpdated( + IDirectManipulationViewport* viewport) { + return S_OK; +} + +HRESULT DirectManipulationEventHandler::OnContentUpdated( + IDirectManipulationViewport* viewport, + IDirectManipulationContent* content) { + float transform[6]; + HRESULT hr = content->GetContentTransform(transform, ARRAYSIZE(transform)); + if (FAILED(hr)) { + FML_LOG(ERROR) << "GetContentTransform failed"; + return S_OK; + } + if (!resetting_) { + // DirectManipulation provides updates with very high precision. If the user + // holds their fingers steady on a trackpad, DirectManipulation sends + // jittery updates. This calculation will reduce the precision of the scale + // value of the event to avoid jitter + const int mantissa_bits_chop = 2; + const float factor = (1 << mantissa_bits_chop) + 1; + float c = factor * transform[0]; + float scale = c - (c - transform[0]); + float pan_x = transform[4]; + float pan_y = transform[5]; + if (owner_->binding_handler_delegate) { + owner_->binding_handler_delegate->OnPointerFlowUpdate( + reinterpret_cast(this), pan_x, pan_y, scale, 0); + } + } + return S_OK; +} + +HRESULT DirectManipulationEventHandler::OnInteraction( + IDirectManipulationViewport2* viewport, + DIRECTMANIPULATION_INTERACTION_TYPE interaction) { + return S_OK; +} + +ULONG STDMETHODCALLTYPE DirectManipulationEventHandler::AddRef() { + RefCountedThreadSafe::AddRef(); + return 0; +} + +ULONG STDMETHODCALLTYPE DirectManipulationEventHandler::Release() { + RefCountedThreadSafe::Release(); + return 0; +} + +DirectManipulationOwner::DirectManipulationOwner(WindowWin32* window) + : window_(window) {} + +int DirectManipulationOwner::Init(unsigned int width, unsigned int height) { + HRESULT hr = CoCreateInstance( + CLSID_DirectManipulationManager, nullptr, CLSCTX_INPROC_SERVER, + IID_IDirectManipulationManager, manager_.put_void()); + if (FAILED(hr)) { + FML_LOG(ERROR) + << "CoCreateInstance(CLSID_DirectManipulationManager) failed"; + manager_ = nullptr; + return -1; + } + + hr = manager_->GetUpdateManager(IID_IDirectManipulationUpdateManager, + updateManager_.put_void()); + if (FAILED(hr)) { + FML_LOG(ERROR) << "GetUpdateManager failed"; + manager_ = nullptr; + updateManager_ = nullptr; + return -1; + } + + hr = manager_->CreateViewport(nullptr, window_->GetWindowHandle(), + IID_IDirectManipulationViewport, + viewport_.put_void()); + if (FAILED(hr)) { + FML_LOG(ERROR) << "CreateViewport failed"; + manager_ = nullptr; + updateManager_ = nullptr; + viewport_ = nullptr; + return -1; + } + + DIRECTMANIPULATION_CONFIGURATION configuration = + DIRECTMANIPULATION_CONFIGURATION_INTERACTION | + DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X | + DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y | + DIRECTMANIPULATION_CONFIGURATION_SCALING; + + hr = viewport_->ActivateConfiguration(configuration); + if (FAILED(hr)) { + FML_LOG(ERROR) << "ActivateConfiguration failed"; + manager_ = nullptr; + updateManager_ = nullptr; + viewport_ = nullptr; + return -1; + } + + hr = viewport_->SetViewportOptions( + DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE); + if (FAILED(hr)) { + FML_LOG(ERROR) << "SetViewportOptions failed"; + manager_ = nullptr; + updateManager_ = nullptr; + viewport_ = nullptr; + return -1; + } + + handler_ = fml::MakeRefCounted(window_, this); + + hr = viewport_->AddEventHandler(window_->GetWindowHandle(), handler_.get(), + &viewportHandlerCookie_); + if (FAILED(hr)) { + FML_LOG(ERROR) << "AddEventHandler failed"; + manager_ = nullptr; + updateManager_ = nullptr; + viewport_ = nullptr; + return -1; + } + + RECT rect = {0, 0, (LONG)width, (LONG)height}; + hr = viewport_->SetViewportRect(&rect); + if (FAILED(hr)) { + FML_LOG(ERROR) << "SetViewportRect failed"; + manager_ = nullptr; + updateManager_ = nullptr; + viewport_ = nullptr; + return -1; + } + + hr = manager_->Activate(window_->GetWindowHandle()); + if (FAILED(hr)) { + FML_LOG(ERROR) << "manager_->Activate failed"; + manager_ = nullptr; + updateManager_ = nullptr; + viewport_ = nullptr; + return -1; + } + + hr = viewport_->Enable(); + if (FAILED(hr)) { + FML_LOG(ERROR) << "viewport_->Enable failed"; + manager_ = nullptr; + updateManager_ = nullptr; + viewport_ = nullptr; + return -1; + } + + hr = updateManager_->Update(nullptr); + if (FAILED(hr)) { + FML_LOG(ERROR) << "updateManager_->Update failed"; + manager_ = nullptr; + updateManager_ = nullptr; + viewport_ = nullptr; + return -1; + } + + return 0; +} + +void DirectManipulationOwner::ResizeViewport(unsigned int width, + unsigned int height) { + if (viewport_) { + RECT rect = {0, 0, (LONG)width, (LONG)height}; + HRESULT hr = viewport_->SetViewportRect(&rect); + if (FAILED(hr)) { + FML_LOG(ERROR) << "SetViewportRect failed"; + } + } +} + +void DirectManipulationOwner::Destroy() { + if (handler_) { + handler_->window_ = nullptr; + handler_->owner_ = nullptr; + } + + HRESULT hr; + if (viewport_) { + hr = viewport_->Disable(); + if (FAILED(hr)) { + FML_LOG(ERROR) << "viewport_->Stop failed"; + } + + hr = viewport_->Disable(); + if (FAILED(hr)) { + FML_LOG(ERROR) << "viewport_->Disable failed"; + } + + hr = viewport_->RemoveEventHandler(viewportHandlerCookie_); + if (FAILED(hr)) { + FML_LOG(ERROR) << "viewport_->RemoveEventHandler failed"; + } + + hr = viewport_->Abandon(); + if (FAILED(hr)) { + FML_LOG(ERROR) << "viewport_->Abandon failed"; + } + } + + if (window_ && manager_) { + hr = manager_->Deactivate(window_->GetWindowHandle()); + if (FAILED(hr)) { + FML_LOG(ERROR) << "manager_->Deactivate failed"; + } + } + + handler_ = nullptr; + viewport_ = nullptr; + updateManager_ = nullptr; + manager_ = nullptr; + window_ = nullptr; +} + +void DirectManipulationOwner::SetContact(UINT contactId) { + if (viewport_) { + viewport_->SetContact(contactId); + } +} + +void DirectManipulationOwner::SetBindingHandlerDelegate( + WindowBindingHandlerDelegate* delegate) { + binding_handler_delegate = delegate; +} + +void DirectManipulationOwner::Update() { + if (updateManager_) { + HRESULT hr = updateManager_->Update(nullptr); + if (FAILED(hr)) { + FML_LOG(ERROR) << "updateManager_->Update failed"; + auto error = GetLastError(); + FML_LOG(ERROR) << error; + LPWSTR message = nullptr; + size_t size = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message), 0, NULL); + FML_LOG(ERROR) << message; + } + } +} + +} // namespace flutter diff --git a/shell/platform/windows/direct_manipulation.h b/shell/platform/windows/direct_manipulation.h new file mode 100644 index 0000000000000..4bd58d1377a0a --- /dev/null +++ b/shell/platform/windows/direct_manipulation.h @@ -0,0 +1,83 @@ +// 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_DIRECT_MANIPULATION_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_DIRECT_MANIPULATION_H_ + +#include "flutter/fml/memory/ref_counted.h" + +#include +#include "directmanipulation.h" + +namespace flutter { + +class WindowWin32; +class WindowBindingHandlerDelegate; + +class DirectManipulationEventHandler; + +class DirectManipulationOwner { + public: + explicit DirectManipulationOwner(WindowWin32* window); + int Init(unsigned int width, unsigned int height); + void ResizeViewport(unsigned int width, unsigned int height); + void SetBindingHandlerDelegate( + WindowBindingHandlerDelegate* binding_handler_delegate); + void SetContact(UINT contactId); + void Update(); + void Destroy(); + WindowBindingHandlerDelegate* binding_handler_delegate; + + private: + WindowWin32* window_; + DWORD viewportHandlerCookie_; + winrt::com_ptr manager_; + winrt::com_ptr updateManager_; + winrt::com_ptr viewport_; + fml::RefPtr handler_; +}; + +class DirectManipulationEventHandler + : public fml::RefCountedThreadSafe, + public IDirectManipulationViewportEventHandler, + public IDirectManipulationInteractionEventHandler { + friend class DirectManipulationOwner; + FML_FRIEND_REF_COUNTED_THREAD_SAFE(DirectManipulationEventHandler); + FML_FRIEND_MAKE_REF_COUNTED(DirectManipulationEventHandler); + + public: + explicit DirectManipulationEventHandler(WindowWin32* window, + DirectManipulationOwner* owner) + : window_(window), owner_(owner) {} + + STDMETHODIMP QueryInterface(REFIID iid, void** ppv) override; + + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; + + HRESULT STDMETHODCALLTYPE + OnViewportStatusChanged(IDirectManipulationViewport* viewport, + DIRECTMANIPULATION_STATUS current, + DIRECTMANIPULATION_STATUS previous) override; + + HRESULT STDMETHODCALLTYPE + OnViewportUpdated(IDirectManipulationViewport* viewport) override; + + HRESULT STDMETHODCALLTYPE + OnContentUpdated(IDirectManipulationViewport* viewport, + IDirectManipulationContent* content) override; + + HRESULT STDMETHODCALLTYPE + OnInteraction(IDirectManipulationViewport2* viewport, + DIRECTMANIPULATION_INTERACTION_TYPE interaction) override; + + private: + WindowWin32* window_; + DirectManipulationOwner* owner_; + bool resetting_ = false; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_DIRECT_MANIPULATION_H_ diff --git a/shell/platform/windows/direct_manipulation_unittests.cc b/shell/platform/windows/direct_manipulation_unittests.cc new file mode 100644 index 0000000000000..e6006eb992f1d --- /dev/null +++ b/shell/platform/windows/direct_manipulation_unittests.cc @@ -0,0 +1,195 @@ +// 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/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() {} + + // Prevent copying. + MockIDirectManipulationViewport(MockIDirectManipulationViewport const&) = + delete; + MockIDirectManipulationViewport& operator=( + MockIDirectManipulationViewport const&) = delete; + 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)); +}; + +class MockIDirectManipulationContent : public IDirectManipulationContent { + public: + MockIDirectManipulationContent() {} + + // Prevent copying. + MockIDirectManipulationContent(MockIDirectManipulationContent const&) = + delete; + MockIDirectManipulationContent& operator=( + MockIDirectManipulationContent const&) = delete; + 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)); +}; + +TEST(DirectManipulationTest, TestGesture) { + MockIDirectManipulationContent content; + MockWindowBindingHandlerDelegate delegate; + MockIDirectManipulationViewport viewport; + const float scale = 1.1; + const float scale_rounded = 1.0999999046325684; + 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(nullptr); + owner->SetBindingHandlerDelegate(&delegate); + auto handler = + fml::MakeRefCounted(nullptr, owner.get()); + int32_t device_id = reinterpret_cast(handler.get()); + EXPECT_CALL(delegate, OnPointerFlowStart(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, + OnPointerFlowUpdate(device_id, pan_x, pan_y, scale_rounded, 0)); + handler->OnContentUpdated((IDirectManipulationViewport*)&viewport, + (IDirectManipulationContent*)&content); + EXPECT_CALL(delegate, OnPointerFlowEnd(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 diff --git a/shell/platform/windows/flutter_window_win32.cc b/shell/platform/windows/flutter_window_win32.cc index 31d551a59a8b4..2919c3af37c44 100644 --- a/shell/platform/windows/flutter_window_win32.cc +++ b/shell/platform/windows/flutter_window_win32.cc @@ -75,6 +75,7 @@ FlutterWindowWin32::~FlutterWindowWin32() {} void FlutterWindowWin32::SetView(WindowBindingHandlerDelegate* window) { binding_handler_delegate_ = window; + direct_manipulation_owner_->SetBindingHandlerDelegate(window); } WindowsRenderTarget FlutterWindowWin32::GetRenderTarget() { diff --git a/shell/platform/windows/flutter_window_win32_unittests.cc b/shell/platform/windows/flutter_window_win32_unittests.cc index a5e6683ce5c1f..aba82565ee770 100644 --- a/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/shell/platform/windows/flutter_window_win32_unittests.cc @@ -11,6 +11,7 @@ #include "flutter/shell/platform/windows/testing/engine_modifier.h" #include "flutter/shell/platform/windows/testing/flutter_window_win32_test.h" #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h" +#include "flutter/shell/platform/windows/testing/mock_window_binding_handler_delegate.h" #include "flutter/shell/platform/windows/testing/test_keyboard.h" #include "flutter/shell/platform/windows/text_input_plugin.h" #include "flutter/shell/platform/windows/text_input_plugin_delegate.h" @@ -184,49 +185,6 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32, } }; -class MockWindowBindingHandlerDelegate : public WindowBindingHandlerDelegate { - public: - MockWindowBindingHandlerDelegate() {} - - // Prevent copying. - MockWindowBindingHandlerDelegate(MockWindowBindingHandlerDelegate const&) = - delete; - MockWindowBindingHandlerDelegate& operator=( - MockWindowBindingHandlerDelegate const&) = delete; - - MOCK_METHOD2(OnWindowSizeChanged, void(size_t, size_t)); - MOCK_METHOD4(OnPointerMove, - void(double, double, FlutterPointerDeviceKind, int32_t)); - MOCK_METHOD5(OnPointerDown, - void(double, - double, - FlutterPointerDeviceKind, - int32_t, - FlutterPointerMouseButtons)); - MOCK_METHOD5(OnPointerUp, - void(double, - double, - FlutterPointerDeviceKind, - int32_t, - FlutterPointerMouseButtons)); - 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_METHOD7(OnScroll, - void(double, - double, - double, - double, - int, - FlutterPointerDeviceKind, - int32_t)); - MOCK_METHOD0(OnPlatformBrightnessChanged, void()); -}; - // A FlutterWindowsView that overrides the RegisterKeyboardHandlers function // to register the keyboard hook handlers that can be spied upon. class TestFlutterWindowsView : public FlutterWindowsView { diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 35cb908bebf9a..f4426fa3108aa 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -203,6 +203,26 @@ void FlutterWindowsView::OnPointerLeave(FlutterPointerDeviceKind device_kind, SendPointerLeave(GetOrCreatePointerState(device_kind, device_id)); } +void FlutterWindowsView::OnPointerFlowStart(int32_t device_id) { + POINT point = GetCursorPosition(); + SendPointerFlowStart(device_id, point.x, point.y); +} + +void FlutterWindowsView::OnPointerFlowUpdate(int32_t device_id, + double pan_x, + double pan_y, + double scale, + double angle) { + POINT point = GetCursorPosition(); + SendPointerFlowUpdate(device_id, point.x, point.y, pan_x, pan_y, scale, + angle); +} + +void FlutterWindowsView::OnPointerFlowEnd(int32_t device_id) { + POINT point = GetCursorPosition(); + SendPointerFlowEnd(device_id, point.x, point.y); +} + void FlutterWindowsView::OnText(const std::u16string& text) { SendText(text); } @@ -371,6 +391,50 @@ void FlutterWindowsView::SendPointerLeave(PointerState* state) { SendPointerEventWithData(event, state); } +void FlutterWindowsView::SendPointerFlowStart(int32_t device_id, + double x, + double y) { + auto state = + GetOrCreatePointerState(kFlutterPointerDeviceKindTouch, device_id); + FlutterPointerEvent event = {}; + event.x = x; + event.y = y; + event.phase = FlutterPointerPhase::kFlowStart; + SendPointerEventWithData(event, state); +} + +void FlutterWindowsView::SendPointerFlowUpdate(int32_t device_id, + double x, + double y, + double pan_x, + double pan_y, + double scale, + double angle) { + auto state = + GetOrCreatePointerState(kFlutterPointerDeviceKindTouch, device_id); + FlutterPointerEvent event = {}; + event.x = x; + event.y = y; + event.pan_x = pan_x; + event.pan_y = pan_y; + event.scale = scale; + event.angle = angle; + event.phase = FlutterPointerPhase::kFlowUpdate; + SendPointerEventWithData(event, state); +} + +void FlutterWindowsView::SendPointerFlowEnd(int32_t device_id, + double x, + double y) { + auto state = + GetOrCreatePointerState(kFlutterPointerDeviceKindTouch, device_id); + FlutterPointerEvent event = {}; + event.x = x; + event.y = y; + event.phase = FlutterPointerPhase::kFlowEnd; + SendPointerEventWithData(event, state); +} + void FlutterWindowsView::SendText(const std::u16string& text) { for (const auto& handler : keyboard_handlers_) { handler->TextHook(this, text); @@ -568,4 +632,11 @@ FlutterWindowsEngine* FlutterWindowsView::GetEngine() { return engine_.get(); } +POINT FlutterWindowsView::GetCursorPosition() { + POINT point; + GetCursorPos(&point); + ScreenToClient(std::get<0>(*GetRenderTarget()), &point); + return point; +} + } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index 1dd820d81529a..7bea3c2e5f6a9 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -123,6 +123,19 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, void OnPointerLeave(FlutterPointerDeviceKind device_kind, int32_t device_id = 0) override; + // |WindowBindingHandlerDelegate| + virtual void OnPointerFlowStart(int32_t device_id) override; + + // |WindowBindingHandlerDelegate| + virtual void OnPointerFlowUpdate(int32_t device_id, + double pan_x, + double pan_y, + double scale, + double angle) override; + + // |WindowBindingHandlerDelegate| + virtual void OnPointerFlowEnd(int32_t device_id) override; + // |WindowBindingHandlerDelegate| void OnText(const std::u16string&) override; @@ -241,6 +254,18 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, // event is called. void SendPointerLeave(PointerState* state); + void SendPointerFlowStart(int32_t device_id, double x, double y); + + void SendPointerFlowUpdate(int32_t device_id, + double x, + double y, + double pan_x, + double pan_y, + double scale, + double angle); + + void SendPointerFlowEnd(int32_t device_id, double x, double y); + // Reports a keyboard character to Flutter engine. void SendText(const std::u16string&); @@ -304,6 +329,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, // Reports platform brightness change to Flutter engine. void SendPlatformBrightnessChanged(); + // Gets the current cursor position to set on trackpad gesture events + POINT GetCursorPosition(); + // Currently configured WindowsRenderTarget for this view used by // surface_manager for creation of render surfaces and bound to the physical // os window. diff --git a/shell/platform/windows/testing/mock_window_binding_handler_delegate.h b/shell/platform/windows/testing/mock_window_binding_handler_delegate.h new file mode 100644 index 0000000000000..9c4ed4d6c186a --- /dev/null +++ b/shell/platform/windows/testing/mock_window_binding_handler_delegate.h @@ -0,0 +1,64 @@ +// 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_TESTING_MOCK_WINDOW_BINDING_HANDLER_DELEGATE_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WINDOW_BINDING_HANDLER_DELEGATE_H_ + +#include "flutter/shell/platform/windows/window_binding_handler_delegate.h" +#include "gmock/gmock.h" + +namespace flutter { +namespace testing { + +class MockWindowBindingHandlerDelegate : public WindowBindingHandlerDelegate { + public: + MockWindowBindingHandlerDelegate() {} + + // Prevent copying. + MockWindowBindingHandlerDelegate(MockWindowBindingHandlerDelegate const&) = + delete; + MockWindowBindingHandlerDelegate& operator=( + MockWindowBindingHandlerDelegate const&) = delete; + + MOCK_METHOD2(OnWindowSizeChanged, void(size_t, size_t)); + MOCK_METHOD4(OnPointerMove, + void(double, double, FlutterPointerDeviceKind, int32_t)); + MOCK_METHOD5(OnPointerDown, + void(double, + double, + FlutterPointerDeviceKind, + int32_t, + FlutterPointerMouseButtons)); + MOCK_METHOD5(OnPointerUp, + void(double, + double, + FlutterPointerDeviceKind, + int32_t, + FlutterPointerMouseButtons)); + MOCK_METHOD2(OnPointerLeave, void(FlutterPointerDeviceKind, int32_t)); + MOCK_METHOD1(OnPointerFlowStart, void(int32_t)); + MOCK_METHOD5(OnPointerFlowUpdate, + void(int32_t, double, double, double, double)); + MOCK_METHOD1(OnPointerFlowEnd, void(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_METHOD7(OnScroll, + void(double, + double, + double, + double, + int, + FlutterPointerDeviceKind, + int32_t)); + MOCK_METHOD0(OnPlatformBrightnessChanged, void()); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WINDOW_BINDING_HANDLER_DELEGATE_H_ diff --git a/shell/platform/windows/window_binding_handler_delegate.h b/shell/platform/windows/window_binding_handler_delegate.h index 7bdedc9d4dbf0..5ec9cfd30131a 100644 --- a/shell/platform/windows/window_binding_handler_delegate.h +++ b/shell/platform/windows/window_binding_handler_delegate.h @@ -45,6 +45,14 @@ class WindowBindingHandlerDelegate { virtual void OnPointerLeave(FlutterPointerDeviceKind device_kind, int32_t device_id) = 0; + virtual void OnPointerFlowStart(int32_t device_id) = 0; + virtual void OnPointerFlowUpdate(int32_t device_id, + double pan_x, + double pan_y, + double scale, + double angle) = 0; + virtual void OnPointerFlowEnd(int32_t device_id) = 0; + // Notifies delegate that backing window has received text. // Typically called by currently configured WindowBindingHandler virtual void OnText(const std::u16string&) = 0; diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index 7735669e05b10..f9e4d60908e2a 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -80,6 +80,17 @@ void WindowWin32::InitializeChild(const char* title, OutputDebugString(message); LocalFree(message); } + DEVMODE dmi; + ZeroMemory(&dmi, sizeof(dmi)); + dmi.dmSize = sizeof(dmi); + if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dmi)) { + framerate_ = dmi.dmDisplayFrequency; + SetTimer(result, kVsyncTimer, 1000 / framerate_, nullptr); + } else { + OutputDebugString(L"Failed to get framerate"); + } + direct_manipulation_owner_ = std::make_unique(this); + direct_manipulation_owner_->Init(width, height); } std::wstring WindowWin32::NarrowToWide(const char* source) { @@ -377,6 +388,24 @@ WindowWin32::HandleMessage(UINT const message, static_cast(WHEEL_DELTA)), 0.0, kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId); break; + case WM_TIMER: + if (wparam == kVsyncTimer) { + direct_manipulation_owner_->Update(); + SetTimer(window_handle_, 1, 1000 / framerate_, nullptr); + return 0; + } + break; + case DM_POINTERHITTEST: { + if (direct_manipulation_owner_) { + UINT contactId = GET_POINTERID_WPARAM(wparam); + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(contactId, &pointerType) && + pointerType == PT_TOUCHPAD) { + direct_manipulation_owner_->SetContact(contactId); + } + } + break; + } case WM_INPUTLANGCHANGE: // TODO(cbracken): pass this to TextInputManager to aid with // language-specific issues. @@ -569,6 +598,9 @@ void WindowWin32::Destroy() { void WindowWin32::HandleResize(UINT width, UINT height) { current_width_ = width; current_height_ = height; + if (direct_manipulation_owner_) { + direct_manipulation_owner_->ResizeViewport(width, height); + } OnResize(width, height); } diff --git a/shell/platform/windows/window_win32.h b/shell/platform/windows/window_win32.h index 3607faf820231..42662960eb2a5 100644 --- a/shell/platform/windows/window_win32.h +++ b/shell/platform/windows/window_win32.h @@ -14,6 +14,7 @@ #include #include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/windows/direct_manipulation.h" #include "flutter/shell/platform/windows/sequential_id_generator.h" #include "flutter/shell/platform/windows/text_input_manager_win32.h" @@ -206,6 +207,8 @@ class WindowWin32 { // Used to process key messages. Exposed for dependency injection. virtual uint32_t Win32MapVkToChar(uint32_t virtual_key); + std::unique_ptr direct_manipulation_owner_; + private: // Release OS resources associated with window. void Destroy(); @@ -260,6 +263,9 @@ class WindowWin32 { // Generates touch point IDs for touch events. SequentialIdGenerator touch_id_generator_; + + const static int kVsyncTimer = 1; + int framerate_ = 1; }; } // namespace flutter