Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Multi-view pointer event #46213

Merged
merged 27 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/glfw/FlutterEmbedderGLFW.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
static double g_pixelRatio = 1.0;
static const size_t kInitialWindowWidth = 800;
static const size_t kInitialWindowHeight = 600;
static constexpr FlutterViewId kImplicitViewId = 0;

static_assert(FLUTTER_ENGINE_VERSION == 1,
"This Flutter Embedder was authored against the stable Flutter "
Expand All @@ -33,6 +34,9 @@ void GLFWcursorPositionCallbackAtPhase(GLFWwindow* window,
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
// This example only supports a single window, therefore we assume the pointer
// event occurred in the only view, the implicit view.
event.view_id = kImplicitViewId;
FlutterEngineSendPointerEvent(
reinterpret_cast<FlutterEngine>(glfwGetWindowUserPointer(window)), &event,
1);
Expand Down
4 changes: 4 additions & 0 deletions examples/glfw_drm/FlutterEmbedderGLFW.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ static const size_t kInitialWindowHeight = 600;
// Maximum damage history - for triple buffering we need to store damage for
// last two frames; Some Android devices (Pixel 4) use quad buffering.
static const int kMaxHistorySize = 10;
static constexpr FlutterViewId kImplicitViewId = 0;

// Keeps track of the most recent frame damages so that existing damage can
// be easily computed.
Expand Down Expand Up @@ -56,6 +57,9 @@ void GLFWcursorPositionCallbackAtPhase(GLFWwindow* window,
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
// This example only supports a single window, therefore we assume the pointer
// event occurred in the only view, the implicit view.
event.view_id = kImplicitViewId;
FlutterEngineSendPointerEvent(
reinterpret_cast<FlutterEngine>(glfwGetWindowUserPointer(window)), &event,
1);
Expand Down
12 changes: 5 additions & 7 deletions lib/ui/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -414,12 +414,9 @@ class PlatformDispatcher {
}
}

// If this value changes, update the encoding code in the following files:
//
// * pointer_data.cc
// * pointer.dart
// * AndroidTouchProcessor.java
static const int _kPointerDataFieldCount = 35;
// This value must match kPointerDataFieldCount in pointer_data.cc. (The
// pointer_data.cc also lists other locations that must be kept consistent.)
static const int _kPointerDataFieldCount = 36;

static PointerDataPacket _unpackPointerDataPacket(ByteData packet) {
const int kStride = Int64List.bytesPerElement;
Expand All @@ -430,7 +427,7 @@ class PlatformDispatcher {
for (int i = 0; i < length; ++i) {
int offset = i * _kPointerDataFieldCount;
data.add(PointerData(
// TODO(goderbauer): Wire up viewId.
// The unpacking code must match the struct in pointer_data.h.
embedderId: packet.getInt64(kStride * offset++, _kFakeHostEndian),
timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)),
change: PointerChange.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)],
Expand Down Expand Up @@ -466,6 +463,7 @@ class PlatformDispatcher {
panDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
scale: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
rotation: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
viewId: packet.getInt64(kStride * offset++, _kFakeHostEndian),
));
assert(offset == (i + 1) * _kPointerDataFieldCount);
}
Expand Down
3 changes: 2 additions & 1 deletion lib/ui/pointer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,8 @@ class PointerData {
'panDeltaX: $panDeltaX, '
'panDeltaY: $panDeltaY, '
'scale: $scale, '
'rotation: $rotation'
'rotation: $rotation, '
'viewId: $viewId'
')';
}
}
Expand Down
11 changes: 11 additions & 0 deletions lib/ui/window/pointer_data.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@

namespace flutter {

// The number of fields of PointerData.
//
// If kPointerDataFieldCount changes, update the corresponding values to:
//
// * _kPointerDataFieldCount in platform_dispatcher.dart
// * POINTER_DATA_FIELD_COUNT in AndroidTouchProcessor.java
//
// (This is a centralized list of all locations that should be kept up-to-date.)
static constexpr int kPointerDataFieldCount = 36;
static constexpr int kBytesPerField = sizeof(int64_t);

static_assert(sizeof(PointerData) == kBytesPerField * kPointerDataFieldCount,
"PointerData has the wrong size");

Expand Down
13 changes: 8 additions & 5 deletions lib/ui/window/pointer_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@

namespace flutter {

// If this value changes, update the pointer data unpacking code in
// platform_dispatcher.dart.
static constexpr int kPointerDataFieldCount = 35;
static constexpr int kBytesPerField = sizeof(int64_t);
// Must match the button constants in events.dart.
enum PointerButtonMouse : int64_t {
kPointerButtonMousePrimary = 1 << 0,
Expand All @@ -32,7 +28,13 @@ enum PointerButtonStylus : int64_t {
kPointerButtonStylusSecondary = 1 << 2,
};

// This structure is unpacked by hooks.dart.
// This structure is unpacked by platform_dispatcher.dart.
//
// If this struct changes, update:
// * kPointerDataFieldCount in pointer_data.cc. (The pointer_data.cc also
// lists out other locations that must be kept consistent.)
// * The functions to create simulated data in
// pointer_data_packet_converter_unittests.cc.
struct alignas(8) PointerData {
// Must match the PointerChange enum in pointer.dart.
enum class Change : int64_t {
Expand Down Expand Up @@ -100,6 +102,7 @@ struct alignas(8) PointerData {
double pan_delta_y;
double scale;
double rotation;
int64_t view_id;

void Clear();
};
Expand Down
23 changes: 23 additions & 0 deletions lib/ui/window/pointer_data_packet_converter_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ void CreateSimulatedPointerData(PointerData& data, // NOLINT
data.platformData = 0;
data.scroll_delta_x = 0.0;
data.scroll_delta_y = 0.0;
data.view_id = 0;
}

void CreateSimulatedMousePointerData(PointerData& data, // NOLINT
Expand Down Expand Up @@ -84,6 +85,7 @@ void CreateSimulatedMousePointerData(PointerData& data, // NOLINT
data.platformData = 0;
data.scroll_delta_x = scroll_delta_x;
data.scroll_delta_y = scroll_delta_y;
data.view_id = 0;
}

void CreateSimulatedTrackpadGestureData(PointerData& data, // NOLINT
Expand Down Expand Up @@ -129,6 +131,7 @@ void CreateSimulatedTrackpadGestureData(PointerData& data, // NOLINT
data.pan_delta_y = 0.0;
data.scale = scale;
data.rotation = rotation;
data.view_id = 0;
}

void UnpackPointerPacket(std::vector<PointerData>& output, // NOLINT
Expand Down Expand Up @@ -713,5 +716,25 @@ TEST(PointerDataPacketConverterTest, CanConvertTrackpadGesture) {
ASSERT_EQ(result[3].synthesized, 0);
}

TEST(PointerDataPacketConverterTest, CanConvertViewId) {
PointerDataPacketConverter converter;
auto packet = std::make_unique<PointerDataPacket>(2);
PointerData data;
CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);
data.view_id = 100;
packet->SetPointerData(0, data);
CreateSimulatedPointerData(data, PointerData::Change::kHover, 0, 1.0, 0.0, 0);
data.view_id = 200;
packet->SetPointerData(1, data);
auto converted_packet = converter.Convert(std::move(packet));

std::vector<PointerData> result;
UnpackPointerPacket(result, std::move(converted_packet));

ASSERT_EQ(result.size(), (size_t)2);
ASSERT_EQ(result[0].view_id, 100);
ASSERT_EQ(result[1].view_id, 200);
}

} // namespace testing
} // namespace flutter
3 changes: 2 additions & 1 deletion lib/web_ui/lib/pointer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ class PointerData {
'panDeltaX: $panDeltaX, '
'panDeltaY: $panDeltaY, '
'scale: $scale, '
'rotation: $rotation'
'rotation: $rotation, '
'viewId: $viewId'
')';
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ public class AndroidTouchProcessor {
int UNKNOWN = 4;
}

// Must match the unpacking code in hooks.dart.
private static final int POINTER_DATA_FIELD_COUNT = 35;
// This value must match kPointerDataFieldCount in pointer_data.cc. (The
// pointer_data.cc also lists other locations that must be kept consistent.)
private static final int POINTER_DATA_FIELD_COUNT = 36;
@VisibleForTesting static final int BYTES_PER_FIELD = 8;

// Default if context is null, chosen to ensure reasonable speed scrolling.
Expand All @@ -92,6 +93,9 @@ public class AndroidTouchProcessor {
// This flag indicates whether the original Android pointer events were batched together.
private static final int POINTER_DATA_FLAG_BATCHED = 1;

// The view ID for the only view in a single-view Flutter app.
private static final int IMPLICIT_VIEW_ID = 0;

@NonNull private final FlutterRenderer renderer;
@NonNull private final MotionEventTracker motionEventTracker;

Expand Down Expand Up @@ -134,6 +138,8 @@ public boolean onTouchEvent(@NonNull MotionEvent event) {
public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transformMatrix) {
int pointerCount = event.getPointerCount();

// The following packing code must match the struct in pointer_data.h.

// Prepare a data packet of the appropriate size and order.
ByteBuffer packet =
ByteBuffer.allocateDirect(pointerCount * POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD);
Expand Down Expand Up @@ -253,6 +259,10 @@ private void addPointerForIndex(
if (pointerChange == -1) {
return;
}
// TODO(dkwingsmt): Use the correct source view ID once Android supports
// multiple views.
// https://github.com/flutter/flutter/issues/134405
final int viewId = IMPLICIT_VIEW_ID;
final int pointerId = event.getPointerId(pointerIndex);

int pointerKind = getPointerDeviceTypeForToolType(event.getToolType(pointerIndex));
Expand Down Expand Up @@ -411,6 +421,7 @@ private void addPointerForIndex(
packet.putDouble(0.0); // pan_delta_y
packet.putDouble(1.0); // scale
packet.putDouble(0.0); // rotation
packet.putLong(viewId); // view_id

if (isTrackpadPan && (panZoomType == PointerChange.PAN_ZOOM_END)) {
ongoingPans.remove(pointerId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#import <os/log.h>
#include <memory>

#include "flutter/common/constants.h"
#include "flutter/fml/memory/weak_ptr.h"
#include "flutter/fml/message_loop.h"
#include "flutter/fml/platform/darwin/platform_version.h"
Expand Down Expand Up @@ -58,6 +59,11 @@
// change. Unfortunately unless you have Werror turned on, incompatible pointers as arguments are
// just a warning.
@interface FlutterViewController () <FlutterBinaryMessenger, UIScrollViewDelegate>
// TODO(dkwingsmt): Make the view ID property public once the iOS shell
// supports multiple views.
// https://github.com/flutter/flutter/issues/138168
@property(nonatomic, readonly) int64_t viewIdentifier;

@property(nonatomic, readwrite, getter=isDisplayingFlutterUI) BOOL displayingFlutterUI;
@property(nonatomic, assign) BOOL isHomeIndicatorHidden;
@property(nonatomic, assign) BOOL isPresentingViewControllerAnimating;
Expand Down Expand Up @@ -148,6 +154,7 @@ @implementation FlutterViewController {

@synthesize displayingFlutterUI = _displayingFlutterUI;
@synthesize prefersStatusBarHidden = _flutterPrefersStatusBarHidden;
@dynamic viewIdentifier;

#pragma mark - Manage and override all designated initializers

Expand Down Expand Up @@ -642,6 +649,12 @@ - (void)installFirstFrameCallback {

#pragma mark - Properties

- (int64_t)viewIdentifier {
// TODO(dkwingsmt): Fill the view ID property with the correct value once the
// iOS shell supports multiple views.
return flutter::kFlutterImplicitViewId;
}

- (UIView*)splashScreenView {
if (!_splashScreenView) {
return nil;
Expand Down Expand Up @@ -928,6 +941,7 @@ - (void)flushOngoingTouches {
pointer_data.change = flutter::PointerData::Change::kCancel;
pointer_data.device = device.longLongValue;
pointer_data.pointer_identifier = 0;
pointer_data.view_id = self.viewIdentifier;

// Anything we put here will be arbitrary since there are no touches.
pointer_data.physical_x = 0;
Expand Down Expand Up @@ -1176,6 +1190,8 @@ - (void)dispatchTouches:(NSSet*)touches

pointer_data.device = reinterpret_cast<int64_t>(touch);

pointer_data.view_id = self.viewIdentifier;

// Pointer will be generated in pointer_data_packet_converter.cc.
pointer_data.pointer_identifier = 0;

Expand Down Expand Up @@ -2393,6 +2409,7 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
pointer_data.device = reinterpret_cast<int64_t>(_continuousScrollingPanGestureRecognizer);
pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
pointer_data.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
pointer_data.view_id = self.viewIdentifier;

if (event.timestamp < _scrollInertiaEventAppKitDeadline) {
// Only send the event if it occured before the expected natural end of gesture momentum.
Expand All @@ -2416,6 +2433,7 @@ - (void)hoverEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4))
flutter::PointerData pointer_data = [self generatePointerDataAtLastMouseLocation];
pointer_data.device = reinterpret_cast<int64_t>(recognizer);
pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
pointer_data.view_id = self.viewIdentifier;

switch (_hoverGestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
Expand Down Expand Up @@ -2454,6 +2472,7 @@ - (void)hoverEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4))
inertia_cancel.device = reinterpret_cast<int64_t>(_continuousScrollingPanGestureRecognizer);
inertia_cancel.kind = flutter::PointerData::DeviceKind::kTrackpad;
inertia_cancel.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
inertia_cancel.view_id = self.viewIdentifier;
packet->SetPointerData(/*i=*/1, inertia_cancel);
[_engine.get() dispatchPointerDataPacket:std::move(packet)];
_scrollInertiaEventStartline = DBL_MAX;
Expand All @@ -2477,6 +2496,7 @@ - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(io
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);
pointer_data.view_id = self.viewIdentifier;

// The translation reported by UIPanGestureRecognizer is the total translation
// generated by the pan gesture since the gesture began. We need to be able
Expand All @@ -2500,6 +2520,7 @@ - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(
flutter::PointerData pointer_data = [self generatePointerDataAtLastMouseLocation];
pointer_data.device = reinterpret_cast<int64_t>(recognizer);
pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
pointer_data.view_id = self.viewIdentifier;
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
Expand Down Expand Up @@ -2548,6 +2569,7 @@ - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)
flutter::PointerData pointer_data = [self generatePointerDataAtLastMouseLocation];
pointer_data.device = reinterpret_cast<int64_t>(recognizer);
pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
pointer_data.view_id = self.viewIdentifier;
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,7 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
.device_kind = deviceKind,
// If a click triggered a synthesized kAdd, don't pass the buttons in that event.
.buttons = phase == kAdd ? 0 : _mouseState.buttons,
.view_id = static_cast<FlutterViewId>(_viewId),
};

if (phase == kPanZoomUpdate) {
Expand Down Expand Up @@ -1049,6 +1050,7 @@ - (void)touchesBeganWithEvent:(NSEvent*)event {
.device = kPointerPanZoomDeviceId,
.signal_kind = kFlutterPointerSignalKindScrollInertiaCancel,
.device_kind = kFlutterPointerDeviceKindTrackpad,
.view_id = static_cast<FlutterViewId>(_viewId),
};

[_engine sendPointerEvent:flutterEvent];
Expand Down
4 changes: 3 additions & 1 deletion shell/platform/embedder/embedder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ extern const intptr_t kPlatformStrongDillSize;
const int32_t kFlutterSemanticsNodeIdBatchEnd = -1;
const int32_t kFlutterSemanticsCustomActionIdBatchEnd = -1;

static constexpr int64_t kFlutterImplicitViewId = 0;
static constexpr FlutterViewId kFlutterImplicitViewId = 0;

// A message channel to send platform-independent FlutterKeyData to the
// framework.
Expand Down Expand Up @@ -2326,6 +2326,8 @@ FlutterEngineResult FlutterEngineSendPointerEvent(
pointer_data.pan_delta_y = 0.0;
pointer_data.scale = SAFE_ACCESS(current, scale, 0.0);
pointer_data.rotation = SAFE_ACCESS(current, rotation, 0.0);
pointer_data.view_id =
SAFE_ACCESS(current, view_id, kFlutterImplicitViewId);
packet->SetPointerData(i, pointer_data);
current = reinterpret_cast<const FlutterPointerEvent*>(
reinterpret_cast<const uint8_t*>(current) + current->struct_size);
Expand Down
Loading