diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index a2b25ba2b017edc73595c5d28dfc10800765849b..c6bcdb3230073ae27b75628169a4032ce770c899 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -344,6 +344,9 @@ FILE: ../../../flutter/lib/ui/window/pointer_data.cc FILE: ../../../flutter/lib/ui/window/pointer_data.h FILE: ../../../flutter/lib/ui/window/pointer_data_packet.cc FILE: ../../../flutter/lib/ui/window/pointer_data_packet.h +FILE: ../../../flutter/lib/ui/window/pointer_data_packet_converter.cc +FILE: ../../../flutter/lib/ui/window/pointer_data_packet_converter.h +FILE: ../../../flutter/lib/ui/window/pointer_data_packet_converter_unittests.cc FILE: ../../../flutter/lib/ui/window/viewport_metrics.cc FILE: ../../../flutter/lib/ui/window/viewport_metrics.h FILE: ../../../flutter/lib/ui/window/window.cc diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 003efe1cb1b66e9b5490e36e9b9179fcea0e8e60..d548d945941841476a3e422b78840bfedd871dd3 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -101,6 +101,8 @@ source_set("ui") { "window/pointer_data.h", "window/pointer_data_packet.cc", "window/pointer_data_packet.h", + "window/pointer_data_packet_converter.cc", + "window/pointer_data_packet_converter.h", "window/viewport_metrics.cc", "window/viewport_metrics.h", "window/window.cc", @@ -161,6 +163,7 @@ if (current_toolchain == host_toolchain) { sources = [ "painting/image_decoder_unittests.cc", + "window/pointer_data_packet_converter_unittests.cc", ] deps = [ diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index cee8de37388a3bb48d0d71fbb2a0acca68c70cc9..83ff3a1059cb4ccae3a313439bdccea477ba6403 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -308,8 +308,9 @@ void _invoke3(void callback(A1 a1, A2 a2, A3 a3), Zone zone, A1 arg1 // If this value changes, update the encoding code in the following files: // // * pointer_data.cc -// * FlutterView.java -const int _kPointerDataFieldCount = 24; +// * pointers.dart +// * AndroidTouchProcessor.java +const int _kPointerDataFieldCount = 28; PointerDataPacket _unpackPointerDataPacket(ByteData packet) { const int kStride = Int64List.bytesPerElement; @@ -325,10 +326,14 @@ PointerDataPacket _unpackPointerDataPacket(ByteData packet) { kind: PointerDeviceKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], signalKind: PointerSignalKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], device: packet.getInt64(kStride * offset++, _kFakeHostEndian), + pointerIdentifier: packet.getInt64(kStride * offset++, _kFakeHostEndian), physicalX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), physicalY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), buttons: packet.getInt64(kStride * offset++, _kFakeHostEndian), obscured: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, + synthesized: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, pressure: packet.getFloat64(kStride * offset++, _kFakeHostEndian), pressureMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), pressureMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), diff --git a/lib/ui/pointer.dart b/lib/ui/pointer.dart index 4879e2f4d7bb7c131efe6db851e65c453542367e..83247ea6092f1b0e148410207d90cf1415171196 100644 --- a/lib/ui/pointer.dart +++ b/lib/ui/pointer.dart @@ -75,10 +75,14 @@ class PointerData { this.kind = PointerDeviceKind.touch, this.signalKind, this.device = 0, + this.pointerIdentifier = 0, this.physicalX = 0.0, this.physicalY = 0.0, + this.physicalDeltaX = 0.0, + this.physicalDeltaY = 0.0, this.buttons = 0, this.obscured = false, + this.synthesized = false, this.pressure = 0.0, this.pressureMin = 0.0, this.pressureMax = 0.0, @@ -111,6 +115,12 @@ class PointerData { /// Unique identifier for the pointing device, reused across interactions. final int device; + /// Unique identifier for the pointer. + /// + /// This field changes for each new pointer down event. Framework uses this + /// identifier to determine hit test result. + final int pointerIdentifier; + /// X coordinate of the position of the pointer, in physical pixels in the /// global coordinate space. final double physicalX; @@ -119,6 +129,12 @@ class PointerData { /// global coordinate space. final double physicalY; + /// The distance of pointer movement on X coordinate in physical pixels. + final double physicalDeltaX; + + /// The distance of pointer movement on Y coordinate in physical pixels. + final double physicalDeltaY; + /// Bit field using the *Button constants (primaryMouseButton, /// secondaryStylusButton, etc). For example, if this has the value 6 and the /// [kind] is [PointerDeviceKind.invertedStylus], then this indicates an @@ -130,6 +146,14 @@ class PointerData { /// implemented.) final bool obscured; + /// Set if this pointer data was synthesized by pointer data packet converter. + /// pointer data packet converter will synthesize additional pointer datas if + /// the input sequence of pointer data is illegal. + /// + /// For example, a down pointer data will be synthesized if the converter receives + /// a move pointer data while the pointer is not previously down. + final bool synthesized; + /// The pressure of the touch as a number ranging from 0.0, indicating a touch /// with no discernible pressure, to 1.0, indicating a touch with "normal" /// pressure, and possibly beyond, indicating a stronger touch. For devices @@ -242,9 +266,13 @@ class PointerData { 'kind: $kind, ' 'signalKind: $signalKind, ' 'device: $device, ' + 'pointerIdentifier: $pointerIdentifier, ' 'physicalX: $physicalX, ' 'physicalY: $physicalY, ' + 'physicalDeltaX: $physicalDeltaX, ' + 'physicalDeltaY: $physicalDeltaY, ' 'buttons: $buttons, ' + 'synthesized: $synthesized, ' 'pressure: $pressure, ' 'pressureMin: $pressureMin, ' 'pressureMax: $pressureMax, ' diff --git a/lib/ui/window/pointer_data.cc b/lib/ui/window/pointer_data.cc index a2d92e8eed36ec462039907baa7a4fd004df75eb..a3292d486bab6fa3fff8cc2190710ed0c67367ee 100644 --- a/lib/ui/window/pointer_data.cc +++ b/lib/ui/window/pointer_data.cc @@ -8,10 +8,7 @@ namespace flutter { -// If this value changes, update the pointer data unpacking code in hooks.dart. -static constexpr int kPointerDataFieldCount = 24; - -static_assert(sizeof(PointerData) == sizeof(int64_t) * kPointerDataFieldCount, +static_assert(sizeof(PointerData) == kBytesPerField * kPointerDataFieldCount, "PointerData has the wrong size"); void PointerData::Clear() { diff --git a/lib/ui/window/pointer_data.h b/lib/ui/window/pointer_data.h index 05adf0682133acb9621525f85603862353c01c2d..96a9e7d5dad916c5470f80db0df6e38b670adc1e 100644 --- a/lib/ui/window/pointer_data.h +++ b/lib/ui/window/pointer_data.h @@ -9,6 +9,9 @@ namespace flutter { +// If this value changes, update the pointer data unpacking code in hooks.dart. +static constexpr int kPointerDataFieldCount = 28; +static constexpr int kBytesPerField = sizeof(int64_t); // Must match the button constants in events.dart. enum PointerButtonMouse : int64_t { kPointerButtonMousePrimary = 1 << 0, @@ -60,10 +63,14 @@ struct alignas(8) PointerData { DeviceKind kind; SignalKind signal_kind; int64_t device; + int64_t pointer_identifier; double physical_x; double physical_y; + double physical_delta_x; + double physical_delta_y; int64_t buttons; int64_t obscured; + int64_t synthesized; double pressure; double pressure_min; double pressure_max; diff --git a/lib/ui/window/pointer_data_packet_converter.cc b/lib/ui/window/pointer_data_packet_converter.cc new file mode 100644 index 0000000000000000000000000000000000000000..c7f643f25417d13462673f91d090a7fcf92fab32 --- /dev/null +++ b/lib/ui/window/pointer_data_packet_converter.cc @@ -0,0 +1,288 @@ +// 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/lib/ui/window/pointer_data_packet_converter.h" +#include "flutter/fml/logging.h" + +#include + +namespace flutter { + +PointerDataPacketConverter::PointerDataPacketConverter() : pointer_(0) {} + +PointerDataPacketConverter::~PointerDataPacketConverter() = default; + +std::unique_ptr PointerDataPacketConverter::Convert( + std::unique_ptr packet) { + size_t kBytesPerPointerData = kPointerDataFieldCount * kBytesPerField; + auto buffer = packet->data(); + size_t buffer_length = buffer.size(); + + std::vector converted_pointers; + // Converts each pointer data in the buffer and stores it in the + // converted_pointers. + for (size_t i = 0; i < buffer_length / kBytesPerPointerData; i++) { + PointerData pointer_data; + memcpy(&pointer_data, &buffer[i * kBytesPerPointerData], + sizeof(PointerData)); + ConvertPointerData(pointer_data, converted_pointers); + } + + // Writes converted_pointers into converted_packet. + auto converted_packet = + std::make_unique(converted_pointers.size()); + size_t count = 0; + for (auto& converted_pointer : converted_pointers) { + converted_packet->SetPointerData(count++, converted_pointer); + } + + return converted_packet; +} + +void PointerDataPacketConverter::ConvertPointerData( + PointerData pointer_data, + std::vector& converted_pointers) { + if (pointer_data.signal_kind == PointerData::SignalKind::kNone) { + switch (pointer_data.change) { + case PointerData::Change::kCancel: { + // Android's three finger gesture will send a cancel event + // to a non-existing pointer. Drops the cancel if pointer + // is not previously added. + // https://github.com/flutter/flutter/issues/20517 + auto iter = states_.find(pointer_data.device); + if (iter != states_.end()) { + PointerState state = iter->second; + FML_DCHECK(state.isDown); + UpdatePointerIdentifier(pointer_data, state, false); + + if (LocationNeedsUpdate(pointer_data, state)) { + // Synthesizes a move event if the location does not match. + PointerData synthesized_move_event = pointer_data; + synthesized_move_event.change = PointerData::Change::kMove; + synthesized_move_event.synthesized = 1; + + UpdateDeltaAndState(synthesized_move_event, state); + converted_pointers.push_back(synthesized_move_event); + } + + state.isDown = false; + states_[pointer_data.device] = state; + converted_pointers.push_back(pointer_data); + } + break; + } + case PointerData::Change::kAdd: { + FML_DCHECK(states_.find(pointer_data.device) == states_.end()); + EnsurePointerState(pointer_data); + converted_pointers.push_back(pointer_data); + break; + } + case PointerData::Change::kRemove: { + // Makes sure we have an existing pointer + auto iter = states_.find(pointer_data.device); + FML_DCHECK(iter != states_.end()); + PointerState state = iter->second; + + if (state.isDown) { + // Synthesizes cancel event if the pointer is down. + PointerData synthesized_cancel_event = pointer_data; + synthesized_cancel_event.change = PointerData::Change::kCancel; + synthesized_cancel_event.synthesized = 1; + UpdatePointerIdentifier(synthesized_cancel_event, state, false); + + state.isDown = false; + states_[synthesized_cancel_event.device] = state; + converted_pointers.push_back(synthesized_cancel_event); + } + + 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; + + UpdateDeltaAndState(synthesized_hover_event, state); + converted_pointers.push_back(synthesized_hover_event); + } + + states_.erase(pointer_data.device); + converted_pointers.push_back(pointer_data); + break; + } + case PointerData::Change::kHover: { + 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; + state = EnsurePointerState(synthesized_add_event); + converted_pointers.push_back(synthesized_add_event); + } else { + state = iter->second; + } + + FML_DCHECK(!state.isDown); + if (LocationNeedsUpdate(pointer_data, state)) { + UpdateDeltaAndState(pointer_data, state); + converted_pointers.push_back(pointer_data); + } + break; + } + case PointerData::Change::kDown: { + auto iter = states_.find(pointer_data.device); + PointerState state; + if (iter == states_.end()) { + // Synthesizes a 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; + state = EnsurePointerState(synthesized_add_event); + converted_pointers.push_back(synthesized_add_event); + } else { + state = iter->second; + } + + FML_DCHECK(!state.isDown); + 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; + + UpdateDeltaAndState(synthesized_hover_event, state); + converted_pointers.push_back(synthesized_hover_event); + } + + UpdatePointerIdentifier(pointer_data, state, true); + state.isDown = true; + states_[pointer_data.device] = state; + converted_pointers.push_back(pointer_data); + break; + } + case PointerData::Change::kMove: { + // Makes sure we have an existing pointer in down state + auto iter = states_.find(pointer_data.device); + FML_DCHECK(iter != states_.end()); + PointerState state = iter->second; + FML_DCHECK(state.isDown); + + if (LocationNeedsUpdate(pointer_data, state)) { + UpdatePointerIdentifier(pointer_data, state, false); + UpdateDeltaAndState(pointer_data, state); + converted_pointers.push_back(pointer_data); + } + break; + } + case PointerData::Change::kUp: { + // Makes sure we have an existing pointer in down state + auto iter = states_.find(pointer_data.device); + FML_DCHECK(iter != states_.end()); + PointerState state = iter->second; + FML_DCHECK(state.isDown); + + UpdatePointerIdentifier(pointer_data, state, false); + + if (LocationNeedsUpdate(pointer_data, state)) { + // Synthesizes a move event if the location does not match. + PointerData synthesized_move_event = pointer_data; + synthesized_move_event.change = PointerData::Change::kMove; + synthesized_move_event.synthesized = 1; + + UpdateDeltaAndState(synthesized_move_event, state); + converted_pointers.push_back(synthesized_move_event); + } + + state.isDown = false; + states_[pointer_data.device] = state; + converted_pointers.push_back(pointer_data); + break; + } + default: { + converted_pointers.push_back(pointer_data); + break; + } + } + } else { + switch (pointer_data.signal_kind) { + case PointerData::SignalKind::kScroll: { + // Makes sure we have an existing pointer + auto iter = states_.find(pointer_data.device); + FML_DCHECK(iter != states_.end()); + + PointerState state = iter->second; + if (LocationNeedsUpdate(pointer_data, state)) { + if (state.isDown) { + // Synthesizes a move event if the pointer is down. + PointerData synthesized_move_event = pointer_data; + synthesized_move_event.signal_kind = PointerData::SignalKind::kNone; + synthesized_move_event.change = PointerData::Change::kMove; + synthesized_move_event.synthesized = 1; + + UpdateDeltaAndState(synthesized_move_event, state); + converted_pointers.push_back(synthesized_move_event); + } else { + // Synthesizes a hover event if the pointer is up. + PointerData synthesized_hover_event = pointer_data; + synthesized_hover_event.signal_kind = + PointerData::SignalKind::kNone; + synthesized_hover_event.change = PointerData::Change::kHover; + synthesized_hover_event.synthesized = 1; + + UpdateDeltaAndState(synthesized_hover_event, state); + converted_pointers.push_back(synthesized_hover_event); + } + } + + converted_pointers.push_back(pointer_data); + break; + } + default: { + // Ignores unknown signal kind. + break; + } + } + } +} + +PointerState PointerDataPacketConverter::EnsurePointerState( + PointerData pointer_data) { + PointerState state; + state.pointer_identifier = 0; + state.isDown = false; + state.physical_x = pointer_data.physical_x; + state.physical_y = pointer_data.physical_y; + states_[pointer_data.device] = state; + return state; +} + +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; + state.physical_x = pointer_data.physical_x; + state.physical_y = pointer_data.physical_y; + states_[pointer_data.device] = state; +} + +bool PointerDataPacketConverter::LocationNeedsUpdate( + const PointerData pointer_data, + const PointerState state) { + return state.physical_x != pointer_data.physical_x || + state.physical_y != pointer_data.physical_y; +} + +void PointerDataPacketConverter::UpdatePointerIdentifier( + PointerData& pointer_data, + PointerState& state, + bool start_new_pointer) { + if (start_new_pointer) { + state.pointer_identifier = ++pointer_; + states_[pointer_data.device] = state; + } + pointer_data.pointer_identifier = state.pointer_identifier; +} + +} // namespace flutter diff --git a/lib/ui/window/pointer_data_packet_converter.h b/lib/ui/window/pointer_data_packet_converter.h new file mode 100644 index 0000000000000000000000000000000000000000..29024d07fbd32d998dbb145fa35c72786248f2d4 --- /dev/null +++ b/lib/ui/window/pointer_data_packet_converter.h @@ -0,0 +1,111 @@ +// 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_LIB_UI_WINDOW_POINTER_DATA_PACKET_CONVERTER_H_ +#define FLUTTER_LIB_UI_WINDOW_POINTER_DATA_PACKET_CONVERTER_H_ + +#include + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/lib/ui/window/pointer_data_packet.h" + +namespace flutter { + +//------------------------------------------------------------------------------ +/// The current information about a pointer. This struct is used by +/// PointerDataPacketConverter to fill in necesarry information for raw pointer +/// packet sent from embedding. +/// +struct PointerState { + int64_t pointer_identifier; + bool isDown; + double physical_x; + double physical_y; +}; + +//------------------------------------------------------------------------------ +/// Converter to convert the raw pointer data packet from the platforms. +/// +/// Framework requires certain information to process pointer data. e.g. pointer +/// identifier and the delta of pointer moment. The converter keeps track each +/// pointer state and fill in those information appropriately. +/// +/// The converter is also resposible for providing a clean pointer data stream. +/// It will attempt to correct the stream if the it contains illegal pointer +/// transitions. +/// +/// Example 1 Missing Add: +/// +/// Down(position x) -> Up(position x) +/// +/// ###After Conversion### +/// +/// Synthesized_Add(position x) -> Down(position x) -> Up(position x) +/// +/// Example 2 Missing another move: +/// +/// Add(position x) -> Down(position x) -> Move(position y) -> +/// Up(position z) +/// +/// ###After Conversion### +/// +/// Add(position x) -> Down(position x) -> Move(position y) -> +/// Synthesized_Move(position z) -> Up(position z) +/// +/// Platform view is the only client that uses this class to convert all the +/// incoming pointer packet and is responsible for the life cycle of its +/// instance. +/// +class PointerDataPacketConverter { + public: + PointerDataPacketConverter(); + ~PointerDataPacketConverter(); + + //---------------------------------------------------------------------------- + /// @brief Converts pointer data packet into a form that framework + /// understands. The raw pointer data packet from embedding does + /// not have sufficient information and may contain illegal + /// pointer transitions. This method will fill out that + /// information and attempt to correct pointer transitions. + /// + /// @param[in] packet The raw pointer packet sent from + /// embedding. + /// + /// @return A full converted packet with all the required information + /// filled. + /// It may contain synthetic pointer data as the result of + /// converter's attempt to correct illegal pointer transitions. + /// + std::unique_ptr Convert( + std::unique_ptr packet); + + private: + std::map states_; + + int64_t pointer_; + + void ConvertPointerData(PointerData pointer_data, + std::vector& converted_pointers); + + PointerState EnsurePointerState(PointerData pointer_data); + + void UpdateDeltaAndState(PointerData& pointer_data, PointerState& state); + + void UpdatePointerIdentifier(PointerData& pointer_data, + PointerState& state, + bool start_new_pointer); + + bool LocationNeedsUpdate(const PointerData pointer_data, + const PointerState state); + + FML_DISALLOW_COPY_AND_ASSIGN(PointerDataPacketConverter); +}; + +} // namespace flutter + +#endif // FLUTTER_LIB_UI_WINDOW_POINTER_DATA_PACKET_CONVERTER_H_ diff --git a/lib/ui/window/pointer_data_packet_converter_unittests.cc b/lib/ui/window/pointer_data_packet_converter_unittests.cc new file mode 100644 index 0000000000000000000000000000000000000000..d18b7f3e474ed7a6679fe428ed321aae5088c261 --- /dev/null +++ b/lib/ui/window/pointer_data_packet_converter_unittests.cc @@ -0,0 +1,537 @@ +// 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/lib/ui/window/pointer_data_packet_converter.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +void CreateSimulatedPointerData(PointerData& data, + PointerData::Change change, + int64_t device, + double dx, + double dy) { + data.time_stamp = 0; + data.change = change; + data.kind = PointerData::DeviceKind::kTouch; + 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; +} + +void CreateSimulatedMousePointerData(PointerData& data, + PointerData::Change change, + PointerData::SignalKind signal_kind, + int64_t device, + double dx, + double dy, + double scroll_delta_x, + double scroll_delta_y) { + data.time_stamp = 0; + data.change = change; + data.kind = PointerData::DeviceKind::kMouse; + data.signal_kind = signal_kind; + 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 = scroll_delta_x; + data.scroll_delta_y = scroll_delta_y; +} + +void UnpackPointerPacket(std::vector& output, + std::unique_ptr packet) { + size_t kBytesPerPointerData = kPointerDataFieldCount * kBytesPerField; + auto buffer = packet->data(); + size_t buffer_length = buffer.size(); + + for (size_t i = 0; i < buffer_length / kBytesPerPointerData; i++) { + PointerData pointer_data; + memcpy(&pointer_data, &buffer[i * kBytesPerPointerData], + sizeof(PointerData)); + output.push_back(pointer_data); + } + packet.reset(); +} + +TEST(PointerDataPacketConverterTest, CanConvetPointerDataPacket) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(6); + PointerData data; + CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kHover, 0, 3.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 3.0, 0.0); + packet->SetPointerData(2, data); + CreateSimulatedPointerData(data, PointerData::Change::kMove, 0, 3.0, 4.0); + packet->SetPointerData(3, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 3.0, 4.0); + packet->SetPointerData(4, data); + CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 3.0, 4.0); + packet->SetPointerData(5, data); + auto converted_packet = converter.Convert(std::move(packet)); + + std::vector result; + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)6); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].synthesized, 0); + + ASSERT_EQ(result[1].change, PointerData::Change::kHover); + ASSERT_EQ(result[1].synthesized, 0); + ASSERT_EQ(result[1].physical_delta_x, 3.0); + ASSERT_EQ(result[1].physical_delta_y, 0.0); + + ASSERT_EQ(result[2].change, PointerData::Change::kDown); + ASSERT_EQ(result[2].pointer_identifier, 1); + ASSERT_EQ(result[2].synthesized, 0); + + ASSERT_EQ(result[3].change, PointerData::Change::kMove); + ASSERT_EQ(result[3].pointer_identifier, 1); + ASSERT_EQ(result[3].synthesized, 0); + ASSERT_EQ(result[3].physical_delta_x, 0.0); + ASSERT_EQ(result[3].physical_delta_y, 4.0); + + ASSERT_EQ(result[4].change, PointerData::Change::kUp); + ASSERT_EQ(result[4].pointer_identifier, 1); + ASSERT_EQ(result[4].synthesized, 0); + + ASSERT_EQ(result[5].change, PointerData::Change::kRemove); + ASSERT_EQ(result[5].synthesized, 0); +} + +TEST(PointerDataPacketConverterTest, CanSynthesizeDownAndUp) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(4); + PointerData data; + CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 3.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 3.0, 4.0); + packet->SetPointerData(2, data); + CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 3.0, 4.0); + packet->SetPointerData(3, data); + auto converted_packet = converter.Convert(std::move(packet)); + + std::vector result; + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)6); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].synthesized, 0); + + // A hover should be synthesized. + ASSERT_EQ(result[1].change, PointerData::Change::kHover); + ASSERT_EQ(result[1].synthesized, 1); + ASSERT_EQ(result[1].physical_delta_x, 3.0); + ASSERT_EQ(result[1].physical_delta_y, 0.0); + + ASSERT_EQ(result[2].change, PointerData::Change::kDown); + ASSERT_EQ(result[2].pointer_identifier, 1); + ASSERT_EQ(result[2].synthesized, 0); + + // A move should be synthesized. + ASSERT_EQ(result[3].change, PointerData::Change::kMove); + ASSERT_EQ(result[3].pointer_identifier, 1); + ASSERT_EQ(result[3].synthesized, 1); + ASSERT_EQ(result[3].physical_delta_x, 0.0); + ASSERT_EQ(result[3].physical_delta_y, 4.0); + + ASSERT_EQ(result[4].change, PointerData::Change::kUp); + ASSERT_EQ(result[4].pointer_identifier, 1); + ASSERT_EQ(result[4].synthesized, 0); + + ASSERT_EQ(result[5].change, PointerData::Change::kRemove); + ASSERT_EQ(result[5].synthesized, 0); +} + +TEST(PointerDataPacketConverterTest, CanUpdatePointerIdentifier) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(7); + PointerData data; + CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 0.0, 0.0); + packet->SetPointerData(2, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0); + packet->SetPointerData(3, data); + CreateSimulatedPointerData(data, PointerData::Change::kMove, 0, 3.0, 0.0); + packet->SetPointerData(4, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 3.0, 0.0); + packet->SetPointerData(5, data); + CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 3.0, 0.0); + packet->SetPointerData(6, data); + auto converted_packet = converter.Convert(std::move(packet)); + + std::vector result; + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)7); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].synthesized, 0); + + ASSERT_EQ(result[1].change, PointerData::Change::kDown); + ASSERT_EQ(result[1].pointer_identifier, 1); + ASSERT_EQ(result[1].synthesized, 0); + + ASSERT_EQ(result[2].change, PointerData::Change::kUp); + ASSERT_EQ(result[2].pointer_identifier, 1); + ASSERT_EQ(result[2].synthesized, 0); + + // Pointer count increase to 2. + ASSERT_EQ(result[3].change, PointerData::Change::kDown); + ASSERT_EQ(result[3].pointer_identifier, 2); + ASSERT_EQ(result[3].synthesized, 0); + + ASSERT_EQ(result[4].change, PointerData::Change::kMove); + ASSERT_EQ(result[4].pointer_identifier, 2); + ASSERT_EQ(result[4].synthesized, 0); + ASSERT_EQ(result[4].physical_delta_x, 3.0); + ASSERT_EQ(result[4].physical_delta_y, 0.0); + + ASSERT_EQ(result[5].change, PointerData::Change::kUp); + ASSERT_EQ(result[5].pointer_identifier, 2); + ASSERT_EQ(result[5].synthesized, 0); + + ASSERT_EQ(result[6].change, PointerData::Change::kRemove); + ASSERT_EQ(result[6].synthesized, 0); +} + +TEST(PointerDataPacketConverterTest, CanWorkWithDifferentDevices) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(12); + PointerData data; + CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedPointerData(data, PointerData::Change::kAdd, 1, 0.0, 0.0); + packet->SetPointerData(2, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 1, 0.0, 0.0); + packet->SetPointerData(3, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 0.0, 0.0); + packet->SetPointerData(4, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0); + packet->SetPointerData(5, data); + CreateSimulatedPointerData(data, PointerData::Change::kMove, 1, 0.0, 4.0); + packet->SetPointerData(6, data); + CreateSimulatedPointerData(data, PointerData::Change::kMove, 0, 3.0, 0.0); + packet->SetPointerData(7, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 1, 0.0, 4.0); + packet->SetPointerData(8, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 3.0, 0.0); + packet->SetPointerData(9, data); + CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 3.0, 0.0); + packet->SetPointerData(10, data); + CreateSimulatedPointerData(data, PointerData::Change::kRemove, 1, 0.0, 4.0); + packet->SetPointerData(11, data); + auto converted_packet = converter.Convert(std::move(packet)); + + std::vector result; + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)12); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].device, 0); + ASSERT_EQ(result[0].synthesized, 0); + + ASSERT_EQ(result[1].change, PointerData::Change::kDown); + ASSERT_EQ(result[1].device, 0); + ASSERT_EQ(result[1].pointer_identifier, 1); + ASSERT_EQ(result[1].synthesized, 0); + + ASSERT_EQ(result[2].change, PointerData::Change::kAdd); + ASSERT_EQ(result[2].device, 1); + ASSERT_EQ(result[2].synthesized, 0); + + ASSERT_EQ(result[3].change, PointerData::Change::kDown); + ASSERT_EQ(result[3].device, 1); + ASSERT_EQ(result[3].pointer_identifier, 2); + ASSERT_EQ(result[3].synthesized, 0); + + ASSERT_EQ(result[4].change, PointerData::Change::kUp); + ASSERT_EQ(result[4].device, 0); + ASSERT_EQ(result[4].pointer_identifier, 1); + ASSERT_EQ(result[4].synthesized, 0); + + ASSERT_EQ(result[5].change, PointerData::Change::kDown); + ASSERT_EQ(result[5].device, 0); + ASSERT_EQ(result[5].pointer_identifier, 3); + ASSERT_EQ(result[5].synthesized, 0); + + ASSERT_EQ(result[6].change, PointerData::Change::kMove); + ASSERT_EQ(result[6].device, 1); + ASSERT_EQ(result[6].pointer_identifier, 2); + ASSERT_EQ(result[6].synthesized, 0); + ASSERT_EQ(result[6].physical_delta_x, 0.0); + ASSERT_EQ(result[6].physical_delta_y, 4.0); + + ASSERT_EQ(result[7].change, PointerData::Change::kMove); + ASSERT_EQ(result[7].device, 0); + ASSERT_EQ(result[7].pointer_identifier, 3); + ASSERT_EQ(result[7].synthesized, 0); + ASSERT_EQ(result[7].physical_delta_x, 3.0); + ASSERT_EQ(result[7].physical_delta_y, 0.0); + + ASSERT_EQ(result[8].change, PointerData::Change::kUp); + ASSERT_EQ(result[8].device, 1); + ASSERT_EQ(result[8].pointer_identifier, 2); + ASSERT_EQ(result[8].synthesized, 0); + + ASSERT_EQ(result[9].change, PointerData::Change::kUp); + ASSERT_EQ(result[9].device, 0); + ASSERT_EQ(result[9].pointer_identifier, 3); + ASSERT_EQ(result[9].synthesized, 0); + + ASSERT_EQ(result[10].change, PointerData::Change::kRemove); + ASSERT_EQ(result[10].device, 0); + ASSERT_EQ(result[10].synthesized, 0); + + ASSERT_EQ(result[11].change, PointerData::Change::kRemove); + ASSERT_EQ(result[11].device, 1); + ASSERT_EQ(result[11].synthesized, 0); +} + +TEST(PointerDataPacketConverterTest, CanSynthesizeAdd) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(2); + PointerData data; + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 330.0, 450.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 0.0, 0.0); + packet->SetPointerData(1, 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); + // A add should be synthesized. + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].physical_x, 330.0); + ASSERT_EQ(result[0].physical_y, 450.0); + ASSERT_EQ(result[0].synthesized, 1); + + ASSERT_EQ(result[1].change, PointerData::Change::kDown); + ASSERT_EQ(result[1].physical_x, 330.0); + ASSERT_EQ(result[1].physical_y, 450.0); + ASSERT_EQ(result[1].synthesized, 0); + + // A move should be synthesized. + ASSERT_EQ(result[2].change, PointerData::Change::kMove); + ASSERT_EQ(result[2].physical_delta_x, -330.0); + ASSERT_EQ(result[2].physical_delta_y, -450.0); + ASSERT_EQ(result[2].physical_x, 0.0); + ASSERT_EQ(result[2].physical_y, 0.0); + ASSERT_EQ(result[2].synthesized, 1); + + ASSERT_EQ(result[3].change, PointerData::Change::kUp); + ASSERT_EQ(result[3].physical_x, 0.0); + ASSERT_EQ(result[3].physical_y, 0.0); + ASSERT_EQ(result[3].synthesized, 0); +} + +TEST(PointerDataPacketConverterTest, CanHandleThreeFingerGesture) { + // Regression test https://github.com/flutter/flutter/issues/20517. + PointerDataPacketConverter converter; + PointerData data; + std::vector result; + // First finger down. + auto packet = std::make_unique(1); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0); + packet->SetPointerData(0, data); + auto converted_packet = converter.Convert(std::move(packet)); + UnpackPointerPacket(result, std::move(converted_packet)); + // Second finger down. + packet = std::make_unique(1); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 1, 33.0, 44.0); + packet->SetPointerData(0, data); + converted_packet = converter.Convert(std::move(packet)); + UnpackPointerPacket(result, std::move(converted_packet)); + // Triggers three cancels. + packet = std::make_unique(3); + CreateSimulatedPointerData(data, PointerData::Change::kCancel, 1, 33.0, 44.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kCancel, 0, 0.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedPointerData(data, PointerData::Change::kCancel, 2, 40.0, 50.0); + packet->SetPointerData(2, data); + converted_packet = converter.Convert(std::move(packet)); + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)6); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].device, 0); + ASSERT_EQ(result[0].physical_x, 0.0); + ASSERT_EQ(result[0].physical_y, 0.0); + ASSERT_EQ(result[0].synthesized, 1); + + ASSERT_EQ(result[1].change, PointerData::Change::kDown); + 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::kAdd); + ASSERT_EQ(result[2].device, 1); + ASSERT_EQ(result[2].physical_x, 33.0); + ASSERT_EQ(result[2].physical_y, 44.0); + ASSERT_EQ(result[2].synthesized, 1); + + ASSERT_EQ(result[3].change, PointerData::Change::kDown); + ASSERT_EQ(result[3].device, 1); + ASSERT_EQ(result[3].physical_x, 33.0); + ASSERT_EQ(result[3].physical_y, 44.0); + ASSERT_EQ(result[3].synthesized, 0); + + ASSERT_EQ(result[4].change, PointerData::Change::kCancel); + ASSERT_EQ(result[4].device, 1); + ASSERT_EQ(result[4].physical_x, 33.0); + ASSERT_EQ(result[4].physical_y, 44.0); + ASSERT_EQ(result[4].synthesized, 0); + + ASSERT_EQ(result[5].change, PointerData::Change::kCancel); + ASSERT_EQ(result[5].device, 0); + ASSERT_EQ(result[5].physical_x, 0.0); + ASSERT_EQ(result[5].physical_y, 0.0); + ASSERT_EQ(result[5].synthesized, 0); + // Third cancel should be dropped +} + +TEST(PointerDataPacketConverterTest, CanConvetScroll) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(5); + PointerData data; + CreateSimulatedMousePointerData(data, PointerData::Change::kAdd, + PointerData::SignalKind::kNone, 0, 0.0, 0.0, + 0.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedMousePointerData(data, PointerData::Change::kAdd, + PointerData::SignalKind::kNone, 1, 0.0, 0.0, + 0.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedMousePointerData(data, PointerData::Change::kDown, + PointerData::SignalKind::kNone, 1, 0.0, 0.0, + 0.0, 0.0); + packet->SetPointerData(2, data); + CreateSimulatedMousePointerData(data, PointerData::Change::kHover, + PointerData::SignalKind::kScroll, 0, 34.0, + 34.0, 30.0, 0.0); + packet->SetPointerData(3, data); + CreateSimulatedMousePointerData(data, PointerData::Change::kHover, + PointerData::SignalKind::kScroll, 1, 49.0, + 49.0, 50.0, 0.0); + packet->SetPointerData(4, data); + auto converted_packet = converter.Convert(std::move(packet)); + + std::vector result; + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)7); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[0].device, 0); + ASSERT_EQ(result[0].physical_x, 0.0); + ASSERT_EQ(result[0].physical_y, 0.0); + ASSERT_EQ(result[0].synthesized, 0); + + ASSERT_EQ(result[1].change, PointerData::Change::kAdd); + ASSERT_EQ(result[1].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[1].device, 1); + 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::kDown); + ASSERT_EQ(result[2].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[2].device, 1); + ASSERT_EQ(result[2].physical_x, 0.0); + ASSERT_EQ(result[2].physical_y, 0.0); + ASSERT_EQ(result[2].synthesized, 0); + + // Converter will synthesize a hover to position. + ASSERT_EQ(result[3].change, PointerData::Change::kHover); + ASSERT_EQ(result[3].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[3].device, 0); + ASSERT_EQ(result[3].physical_x, 34.0); + ASSERT_EQ(result[3].physical_y, 34.0); + ASSERT_EQ(result[3].physical_delta_x, 34.0); + ASSERT_EQ(result[3].physical_delta_y, 34.0); + ASSERT_EQ(result[3].synthesized, 1); + + ASSERT_EQ(result[4].change, PointerData::Change::kHover); + ASSERT_EQ(result[4].signal_kind, PointerData::SignalKind::kScroll); + ASSERT_EQ(result[4].device, 0); + ASSERT_EQ(result[4].physical_x, 34.0); + ASSERT_EQ(result[4].physical_y, 34.0); + ASSERT_EQ(result[4].scroll_delta_x, 30.0); + ASSERT_EQ(result[4].scroll_delta_y, 0.0); + + // Converter will synthesize a move to position. + ASSERT_EQ(result[5].change, PointerData::Change::kMove); + ASSERT_EQ(result[5].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[5].device, 1); + ASSERT_EQ(result[5].physical_x, 49.0); + ASSERT_EQ(result[5].physical_y, 49.0); + ASSERT_EQ(result[5].physical_delta_x, 49.0); + ASSERT_EQ(result[5].physical_delta_y, 49.0); + ASSERT_EQ(result[5].synthesized, 1); + + ASSERT_EQ(result[6].change, PointerData::Change::kHover); + ASSERT_EQ(result[6].signal_kind, PointerData::SignalKind::kScroll); + ASSERT_EQ(result[6].device, 1); + ASSERT_EQ(result[6].physical_x, 49.0); + ASSERT_EQ(result[6].physical_y, 49.0); + ASSERT_EQ(result[6].scroll_delta_x, 50.0); + ASSERT_EQ(result[6].scroll_delta_y, 0.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 9a6a3ba0d3050bd748435b516536eac69cbf7343..1aeb13cc04a59fe921a66b5c91ca5f2f5cc0a5ea 100644 --- a/lib/web_ui/lib/src/ui/pointer.dart +++ b/lib/web_ui/lib/src/ui/pointer.dart @@ -75,10 +75,14 @@ class PointerData { this.kind = PointerDeviceKind.touch, this.signalKind, this.device = 0, + this.pointerIdentifier = 0, this.physicalX = 0.0, this.physicalY = 0.0, + this.physicalDeltaX = 0.0, + this.physicalDeltaY = 0.0, this.buttons = 0, this.obscured = false, + this.synthesized = false, this.pressure = 0.0, this.pressureMin = 0.0, this.pressureMax = 0.0, @@ -111,6 +115,12 @@ class PointerData { /// Unique identifier for the pointing device, reused across interactions. final int device; + /// Unique identifier for the pointer. + /// + /// This field changes for each new pointer down event. Framework uses this + /// identifier to determine hit test result. + final int pointerIdentifier; + /// X coordinate of the position of the pointer, in physical pixels in the /// global coordinate space. final double physicalX; @@ -119,6 +129,12 @@ class PointerData { /// global coordinate space. final double physicalY; + /// The distance of pointer movement on X coordinate in physical pixels. + final double physicalDeltaX; + + /// The distance of pointer movement on Y coordinate in physical pixels. + final double physicalDeltaY; + /// Bit field using the *Button constants (primaryMouseButton, /// secondaryStylusButton, etc). For example, if this has the value 6 and the /// [kind] is [PointerDeviceKind.invertedStylus], then this indicates an @@ -130,6 +146,14 @@ class PointerData { /// implemented.) final bool obscured; + /// Set if this pointer data was synthesized by pointer data packet converter. + /// pointer data packet converter will synthesize additional pointer datas if + /// the input sequence of pointer data is illegal. + /// + /// For example, a down pointer data will be synthesized if the converter receives + /// a move pointer data while the pointer is not previously down. + final bool synthesized; + /// The pressure of the touch as a number ranging from 0.0, indicating a touch /// with no discernible pressure, to 1.0, indicating a touch with "normal" /// pressure, and possibly beyond, indicating a stronger touch. For devices @@ -242,9 +266,13 @@ class PointerData { 'kind: $kind, ' 'signalKind: $signalKind, ' 'device: $device, ' + 'pointerIdentifier: $pointerIdentifier, ' 'physicalX: $physicalX, ' 'physicalY: $physicalY, ' + 'physicalDeltaX: $physicalDeltaX, ' + 'physicalDeltaY: $physicalDeltaY, ' 'buttons: $buttons, ' + 'synthesized: $synthesized, ' 'pressure: $pressure, ' 'pressureMin: $pressureMin, ' 'pressureMax: $pressureMax, ' diff --git a/shell/common/fixtures/shell_test.dart b/shell/common/fixtures/shell_test.dart index 3c27d561e2ccdbc3cf24d684875774d94ecf5877..c6873995b488535da524528bb807c63805c60c3c 100644 --- a/shell/common/fixtures/shell_test.dart +++ b/shell/common/fixtures/shell_test.dart @@ -11,7 +11,7 @@ void main() {} void nativeReportTimingsCallback(List timings) native 'NativeReportTimingsCallback'; void nativeOnBeginFrame(int microseconds) native 'NativeOnBeginFrame'; -void nativeOnPointerDataPacket() native 'NativeOnPointerDataPacket'; +void nativeOnPointerDataPacket(List sequences) native 'NativeOnPointerDataPacket'; @pragma('vm:entry-point') void reportTimingsMain() { @@ -36,7 +36,11 @@ void onBeginFrameMain() { @pragma('vm:entry-point') void onPointerDataPacketMain() { window.onPointerDataPacket = (PointerDataPacket packet) { - nativeOnPointerDataPacket(); + List sequence= []; + for (PointerData data in packet.data) { + sequence.add(PointerChange.values.indexOf(data.change)); + } + nativeOnPointerDataPacket(sequence); }; } diff --git a/shell/common/input_events_unittests.cc b/shell/common/input_events_unittests.cc index 187bde7a35f1ccd0484ee0685b1c7586277775f8..3e08663fff90ee0d104db3a44e63ab441bfef97e 100644 --- a/shell/common/input_events_unittests.cc +++ b/shell/common/input_events_unittests.cc @@ -141,6 +141,40 @@ static void TestSimulatedInputEvents( ASSERT_EQ(events_consumed_at_frame.back(), num_events); } +void CreateSimulatedPointerData(PointerData& data, + PointerData::Change change, + double dx, + double dy) { + data.time_stamp = 0; + data.change = change; + data.kind = PointerData::DeviceKind::kTouch; + data.signal_kind = PointerData::SignalKind::kNone; + data.device = 0; + 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; +} + TEST_F(ShellTest, MissAtMostOneFrameForIrregularInputEvents) { // We don't use `constexpr int frame_time` here because MSVC doesn't handle // it well with lambda capture. @@ -259,5 +293,125 @@ TEST_F(ShellTest, HandlesActualIphoneXsInputEvents) { } } +TEST_F(ShellTest, CanCorrectlyPipePointerPacket) { + // Sets up shell with test fixture. + auto settings = CreateSettingsForFixture(); + std::unique_ptr shell = CreateShell(settings, true); + + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("onPointerDataPacketMain"); + // Sets up native handler. + fml::AutoResetWaitableEvent reportLatch; + std::vector result_sequence; + auto nativeOnPointerDataPacket = [&reportLatch, &result_sequence]( + Dart_NativeArguments args) { + Dart_Handle exception = nullptr; + result_sequence = tonic::DartConverter>::FromArguments( + args, 0, exception); + reportLatch.Signal(); + }; + // Starts engine. + AddNativeCallback("NativeOnPointerDataPacket", + CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket)); + ASSERT_TRUE(configuration.IsValid()); + RunEngine(shell.get(), std::move(configuration)); + // Starts test. + auto packet = std::make_unique(6); + PointerData data; + CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kHover, 3.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 3.0, 0.0); + packet->SetPointerData(2, data); + CreateSimulatedPointerData(data, PointerData::Change::kMove, 3.0, 4.0); + packet->SetPointerData(3, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 3.0, 4.0); + packet->SetPointerData(4, data); + CreateSimulatedPointerData(data, PointerData::Change::kRemove, 3.0, 4.0); + packet->SetPointerData(5, data); + ShellTest::DispatchPointerData(shell.get(), std::move(packet)); + bool will_draw_new_frame; + ShellTest::VSyncFlush(shell.get(), will_draw_new_frame); + + reportLatch.Wait(); + size_t expect_length = 6; + ASSERT_EQ(result_sequence.size(), expect_length); + ASSERT_EQ(PointerData::Change(result_sequence[0]), PointerData::Change::kAdd); + ASSERT_EQ(PointerData::Change(result_sequence[1]), + PointerData::Change::kHover); + ASSERT_EQ(PointerData::Change(result_sequence[2]), + PointerData::Change::kDown); + ASSERT_EQ(PointerData::Change(result_sequence[3]), + PointerData::Change::kMove); + ASSERT_EQ(PointerData::Change(result_sequence[4]), PointerData::Change::kUp); + ASSERT_EQ(PointerData::Change(result_sequence[5]), + PointerData::Change::kRemove); + + // Cleans up shell. + ASSERT_TRUE(DartVMRef::IsInstanceRunning()); + DestroyShell(std::move(shell)); + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); +} + +TEST_F(ShellTest, CanCorrectlySynthesizePointerPacket) { + // Sets up shell with test fixture. + auto settings = CreateSettingsForFixture(); + std::unique_ptr shell = CreateShell(settings, true); + + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("onPointerDataPacketMain"); + // Sets up native handler. + fml::AutoResetWaitableEvent reportLatch; + std::vector result_sequence; + auto nativeOnPointerDataPacket = [&reportLatch, &result_sequence]( + Dart_NativeArguments args) { + Dart_Handle exception = nullptr; + result_sequence = tonic::DartConverter>::FromArguments( + args, 0, exception); + reportLatch.Signal(); + }; + // Starts engine. + AddNativeCallback("NativeOnPointerDataPacket", + CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket)); + ASSERT_TRUE(configuration.IsValid()); + RunEngine(shell.get(), std::move(configuration)); + // Starts test. + auto packet = std::make_unique(4); + PointerData data; + CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 3.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 3.0, 4.0); + packet->SetPointerData(2, data); + CreateSimulatedPointerData(data, PointerData::Change::kRemove, 3.0, 4.0); + packet->SetPointerData(3, data); + ShellTest::DispatchPointerData(shell.get(), std::move(packet)); + bool will_draw_new_frame; + ShellTest::VSyncFlush(shell.get(), will_draw_new_frame); + + reportLatch.Wait(); + size_t expect_length = 6; + ASSERT_EQ(result_sequence.size(), expect_length); + ASSERT_EQ(PointerData::Change(result_sequence[0]), PointerData::Change::kAdd); + // The pointer data packet converter should synthesize a hover event. + ASSERT_EQ(PointerData::Change(result_sequence[1]), + PointerData::Change::kHover); + ASSERT_EQ(PointerData::Change(result_sequence[2]), + PointerData::Change::kDown); + // The pointer data packet converter should synthesize a move event. + ASSERT_EQ(PointerData::Change(result_sequence[3]), + PointerData::Change::kMove); + ASSERT_EQ(PointerData::Change(result_sequence[4]), PointerData::Change::kUp); + ASSERT_EQ(PointerData::Change(result_sequence[5]), + PointerData::Change::kRemove); + + // Cleans up shell. + ASSERT_TRUE(DartVMRef::IsInstanceRunning()); + DestroyShell(std::move(shell)); + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); +} + } // namespace testing } // namespace flutter diff --git a/shell/common/platform_view.cc b/shell/common/platform_view.cc index 058ad55d0c8018d1fe2ee303fca7a2c92e846f20..3c4ed02998536f75f7002aac812a8995fa26a87a 100644 --- a/shell/common/platform_view.cc +++ b/shell/common/platform_view.cc @@ -38,7 +38,8 @@ void PlatformView::DispatchPlatformMessage( void PlatformView::DispatchPointerDataPacket( std::unique_ptr packet) { - delegate_.OnPlatformViewDispatchPointerDataPacket(std::move(packet)); + delegate_.OnPlatformViewDispatchPointerDataPacket( + pointer_data_packet_converter_.Convert(std::move(packet))); } void PlatformView::DispatchSemanticsAction(int32_t id, diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index ad4170f776a79b2faaafa6b346d582200bc206c1..0ef0b193b9cfa05d3f641f0860828f65b485fc49 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -15,6 +15,7 @@ #include "flutter/lib/ui/semantics/semantics_node.h" #include "flutter/lib/ui/window/platform_message.h" #include "flutter/lib/ui/window/pointer_data_packet.h" +#include "flutter/lib/ui/window/pointer_data_packet_converter.h" #include "flutter/lib/ui/window/viewport_metrics.h" #include "flutter/shell/common/pointer_data_dispatcher.h" #include "flutter/shell/common/surface.h" @@ -544,6 +545,7 @@ class PlatformView { PlatformView::Delegate& delegate_; const TaskRunners task_runners_; + PointerDataPacketConverter pointer_data_packet_converter_; SkISize size_; fml::WeakPtrFactory weak_factory_; diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 51370d082862b6a4097b350c15c4af0b1b9a3954..ed7f1febaf51ca8c8fa78e7ef4fc25a448096ed1 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -173,12 +173,19 @@ void ShellTest::PumpOneFrame(Shell* shell, } void ShellTest::DispatchFakePointerData(Shell* shell) { + auto packet = std::make_unique(1); + DispatchPointerData(shell, std::move(packet)); +} + +void ShellTest::DispatchPointerData(Shell* shell, + std::unique_ptr packet) { fml::AutoResetWaitableEvent latch; - shell->GetTaskRunners().GetPlatformTaskRunner()->PostTask([&latch, shell]() { - auto packet = std::make_unique(1); - shell->OnPlatformViewDispatchPointerDataPacket(std::move(packet)); - latch.Signal(); - }); + shell->GetTaskRunners().GetPlatformTaskRunner()->PostTask( + [&latch, shell, &packet]() { + // Goes through PlatformView to ensure packet is corrected converted. + shell->GetPlatformView()->DispatchPointerDataPacket(std::move(packet)); + latch.Signal(); + }); latch.Wait(); } diff --git a/shell/common/shell_test.h b/shell/common/shell_test.h index fdee9653b71ce59a5e84502b47ce8b5d6d535bc4..270ea812dbf8acfd1289f0c57c0a71a67fe625a3 100644 --- a/shell/common/shell_test.h +++ b/shell/common/shell_test.h @@ -62,7 +62,8 @@ class ShellTest : public ThreadTest { LayerTreeBuilder = {}); static void DispatchFakePointerData(Shell* shell); - + static void DispatchPointerData(Shell* shell, + std::unique_ptr packet); // Declare |UnreportedTimingsCount|, |GetNeedsReportTimings| and // |SetNeedsReportTimings| inside |ShellTest| mainly for easier friend class // declarations as shell unit tests and Shell are in different name spaces. diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index 51dffdaffe918da22df32cafad3c6c4f5c058763..10798cb9d795764e2bb8786d6ba5b0cb942a3329 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -66,7 +66,7 @@ public class AndroidTouchProcessor { } // Must match the unpacking code in hooks.dart. - private static final int POINTER_DATA_FIELD_COUNT = 24; + private static final int POINTER_DATA_FIELD_COUNT = 28; private static final int BYTES_PER_FIELD = 8; // This value must match the value in framework's platform_view.dart. @@ -198,8 +198,11 @@ public class AndroidTouchProcessor { packet.putLong(pointerKind); // kind packet.putLong(signalKind); // signal_kind packet.putLong(event.getPointerId(pointerIndex)); // device + packet.putLong(0); // pointer_identifier, will be generated in pointer_data_packet_converter.cc. packet.putDouble(event.getX(pointerIndex)); // physical_x packet.putDouble(event.getY(pointerIndex)); // 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) { @@ -220,6 +223,8 @@ public class AndroidTouchProcessor { packet.putLong(0); // obscured + packet.putLong(0); // synthesized + packet.putDouble(event.getPressure(pointerIndex)); // pressure double pressureMin = 0.0; double pressureMax = 1.0; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 1af3dc648f34d01e5e96cde0df800d2efca88431..8e351383b0ff692d8d2dba39ef3f3385bdf8cb00 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -507,10 +507,13 @@ typedef enum UIAccessibilityContrast : NSInteger { pointer_data.change = flutter::PointerData::Change::kCancel; pointer_data.kind = flutter::PointerData::DeviceKind::kTouch; pointer_data.device = device.longLongValue; + pointer_data.pointer_identifier = 0; // Anything we put here will be arbitrary since there are no touches. pointer_data.physical_x = 0; pointer_data.physical_y = 0; + pointer_data.physical_delta_x = 0.0; + pointer_data.physical_delta_y = 0.0; pointer_data.pressure = 1.0; pointer_data.pressure_max = 1.0; @@ -629,9 +632,16 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) pointer_data.device = reinterpret_cast(touch); + // Pointer will be generated in pointer_data_packet_converter.cc. + pointer_data.pointer_identifier = 0; + pointer_data.physical_x = windowCoordinates.x * scale; pointer_data.physical_y = windowCoordinates.y * scale; + // Delta will be generated in pointer_data_packet_converter.cc. + pointer_data.physical_delta_x = 0.0; + pointer_data.physical_delta_y = 0.0; + NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device]; // Track touches that began and not yet stopped so we can flush them // if the view controller goes away. diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index b277c56ee7a5ea338890881b7e0beaab561068c1..36a4785954e396d9b9bd56e7c2e76cf6f41e1ed1 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -1121,7 +1121,12 @@ FlutterEngineResult FlutterEngineSendPointerEvent( SAFE_ACCESS(current, phase, FlutterPointerPhase::kCancel)); pointer_data.physical_x = SAFE_ACCESS(current, x, 0.0); pointer_data.physical_y = SAFE_ACCESS(current, y, 0.0); + // Delta will be generated in pointer_data_packet_converter.cc. + pointer_data.physical_delta_x = 0.0; + pointer_data.physical_delta_y = 0.0; pointer_data.device = SAFE_ACCESS(current, device, 0); + // Pointer identifier will be generated in pointer_data_packet_converter.cc. + pointer_data.pointer_identifier = 0; pointer_data.signal_kind = ToPointerDataSignalKind( SAFE_ACCESS(current, signal_kind, kFlutterPointerSignalKindNone)); pointer_data.scroll_delta_x = SAFE_ACCESS(current, scroll_delta_x, 0.0);