diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index f4840689031b0..5f3e0812b12ea 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -371,7 +371,7 @@ class PlatformDispatcher { // * pointer_data.cc // * pointer.dart // * AndroidTouchProcessor.java - static const int _kPointerDataFieldCount = 35; + static const int _kPointerDataFieldCount = 36; static PointerDataPacket _unpackPointerDataPacket(ByteData packet) { const int kStride = Int64List.bytesPerElement; @@ -417,6 +417,7 @@ class PlatformDispatcher { panDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), scale: packet.getFloat64(kStride * offset++, _kFakeHostEndian), rotation: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + preferredStylusAuxiliaryAction: PointerPreferredStylusAuxiliaryAction.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], )); assert(offset == (i + 1) * _kPointerDataFieldCount); } diff --git a/lib/ui/pointer.dart b/lib/ui/pointer.dart index 608a4d49aded5..4585fbcbf0a87 100644 --- a/lib/ui/pointer.dart +++ b/lib/ui/pointer.dart @@ -133,10 +133,31 @@ enum PointerSignalKind { /// A pointer-generated scale event (e.g. trackpad pinch). scale, + /// A stylus generated action (e.g. double tap on Apple Pencil 2) + stylusAuxiliaryAction, + /// An unknown pointer signal kind. unknown } + /// The preferred action for stylus action +enum PointerPreferredStylusAuxiliaryAction { + /// Ignore pointer input + ignore, + + /// Show colour palette if available + showColorPalette, + + /// Switch to eraser if available + switchEraser, + + /// Switch to previous tool + switchPrevious, + + /// unknown preferred action + unknown, +} + /// Information about the state of a pointer. class PointerData { /// Creates an object that represents the state of a pointer. @@ -176,6 +197,7 @@ class PointerData { this.panDeltaY = 0.0, this.scale = 0.0, this.rotation = 0.0, + this.preferredStylusAuxiliaryAction = PointerPreferredStylusAuxiliaryAction.ignore, }); /// Unique identifier that ties the [PointerEvent] to embedder event created it. @@ -374,6 +396,11 @@ class PointerData { /// The current angle of the pan/zoom in radians, with 0.0 as the initial angle. final double rotation; + /// For events with signal kind of stylusAuxiliaryAction + /// + /// The current preferred action for stylusAuxiliaryAction, with ignore as the default. + final PointerPreferredStylusAuxiliaryAction preferredStylusAuxiliaryAction; + @override String toString() => 'PointerData(x: $physicalX, y: $physicalY)'; @@ -413,7 +440,8 @@ class PointerData { 'panDeltaX: $panDeltaX, ' 'panDeltaY: $panDeltaY, ' 'scale: $scale, ' - 'rotation: $rotation' + 'rotation: $rotation, ' + 'preferredStylusAuxiliaryAction: $preferredStylusAuxiliaryAction' ')'; } } diff --git a/lib/ui/window/pointer_data.h b/lib/ui/window/pointer_data.h index 1f323cc4f8169..1dc159554ad74 100644 --- a/lib/ui/window/pointer_data.h +++ b/lib/ui/window/pointer_data.h @@ -11,7 +11,7 @@ namespace flutter { // If this value changes, update the pointer data unpacking code in // platform_dispatcher.dart. -static constexpr int kPointerDataFieldCount = 35; +static constexpr int kPointerDataFieldCount = 36; static constexpr int kBytesPerField = sizeof(int64_t); // Must match the button constants in events.dart. enum PointerButtonMouse : int64_t { @@ -63,6 +63,16 @@ struct alignas(8) PointerData { kScroll, kScrollInertiaCancel, kScale, + kStylusAuxiliaryAction, + }; + + // Must match the PreferredStylusAuxiliaryAction enum in pointer.dart. + enum class PreferredStylusAuxiliaryAction : int64_t { + kIgnore, + kShowColorPalette, + kSwitchEraser, + kSwitchPrevious, + kUnknown }; int64_t embedder_id; @@ -100,6 +110,7 @@ struct alignas(8) PointerData { double pan_delta_y; double scale; double rotation; + PreferredStylusAuxiliaryAction preferred_auxiliary_stylus_action; void Clear(); }; diff --git a/lib/ui/window/pointer_data_packet_converter.cc b/lib/ui/window/pointer_data_packet_converter.cc index 6750a7da22761..d3b19f64bea05 100644 --- a/lib/ui/window/pointer_data_packet_converter.cc +++ b/lib/ui/window/pointer_data_packet_converter.cc @@ -293,6 +293,7 @@ void PointerDataPacketConverter::ConvertPointerData( switch (pointer_data.signal_kind) { case PointerData::SignalKind::kScroll: case PointerData::SignalKind::kScrollInertiaCancel: + case PointerData::SignalKind::kStylusAuxiliaryAction: case PointerData::SignalKind::kScale: { // Makes sure we have an existing pointer auto iter = states_.find(pointer_data.device); diff --git a/lib/ui/window/pointer_data_packet_converter_unittests.cc b/lib/ui/window/pointer_data_packet_converter_unittests.cc index 7c4c98162a3eb..235f2f9a39c52 100644 --- a/lib/ui/window/pointer_data_packet_converter_unittests.cc +++ b/lib/ui/window/pointer_data_packet_converter_unittests.cc @@ -45,6 +45,8 @@ void CreateSimulatedPointerData(PointerData& data, // NOLINT data.platformData = 0; data.scroll_delta_x = 0.0; data.scroll_delta_y = 0.0; + data.preferred_auxiliary_stylus_action = + PointerData::PreferredStylusAuxiliaryAction::kIgnore; } void CreateSimulatedMousePointerData(PointerData& data, // NOLINT @@ -84,6 +86,8 @@ void CreateSimulatedMousePointerData(PointerData& data, // NOLINT data.platformData = 0; data.scroll_delta_x = scroll_delta_x; data.scroll_delta_y = scroll_delta_y; + data.preferred_auxiliary_stylus_action = + PointerData::PreferredStylusAuxiliaryAction::kIgnore; } void CreateSimulatedTrackpadGestureData(PointerData& data, // NOLINT @@ -129,6 +133,8 @@ void CreateSimulatedTrackpadGestureData(PointerData& data, // NOLINT data.pan_delta_y = 0.0; data.scale = scale; data.rotation = rotation; + data.preferred_auxiliary_stylus_action = + PointerData::PreferredStylusAuxiliaryAction::kIgnore; } void UnpackPointerPacket(std::vector& output, // NOLINT diff --git a/lib/web_ui/lib/pointer.dart b/lib/web_ui/lib/pointer.dart index 542f5c70c90c3..fd7981bc354a7 100644 --- a/lib/web_ui/lib/pointer.dart +++ b/lib/web_ui/lib/pointer.dart @@ -34,6 +34,24 @@ enum PointerSignalKind { unknown } + /// The preferred action for stylus action +enum PointerPreferredStylusAuxiliaryAction { + /// Ignore pointer input + ignore, + + /// Show colour palette if available + showColorPalette, + + /// Switch to eraser if available + switchEraser, + + /// Switch to previous tool + switchPrevious, + + /// unknown preferred action + unknown, +} + class PointerData { const PointerData({ this.embedderId = 0, @@ -71,6 +89,7 @@ class PointerData { this.panDeltaY = 0.0, this.scale = 0.0, this.rotation = 0.0, + this.preferredStylusAuxiliaryAction = PointerPreferredStylusAuxiliaryAction.ignore, }); final int embedderId; final Duration timeStamp; @@ -107,6 +126,7 @@ class PointerData { final double panDeltaY; final double scale; final double rotation; + final PointerPreferredStylusAuxiliaryAction preferredStylusAuxiliaryAction; @override String toString() => 'PointerData(x: $physicalX, y: $physicalY)'; @@ -145,7 +165,8 @@ class PointerData { 'panDeltaX: $panDeltaX, ' 'panDeltaY: $panDeltaY, ' 'scale: $scale, ' - 'rotation: $rotation' + 'rotation: $rotation, ' + 'preferredStylusAuxiliaryAction: $preferredStylusAuxiliaryAction' ')'; } } diff --git a/shell/common/input_events_unittests.cc b/shell/common/input_events_unittests.cc index 3f117c006c65f..d9d1e52618788 100644 --- a/shell/common/input_events_unittests.cc +++ b/shell/common/input_events_unittests.cc @@ -176,6 +176,8 @@ void CreateSimulatedPointerData(PointerData& data, data.platformData = 0; data.scroll_delta_x = 0.0; data.scroll_delta_y = 0.0; + data.preferred_auxiliary_stylus_action = + PointerData::PreferredStylusAuxiliaryAction::kIgnore; } TEST_F(ShellTest, MissAtMostOneFrameForIrregularInputEvents) { diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index b3db5df1eb99b..ea36686e7c8b7 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -66,6 +66,7 @@ public class AndroidTouchProcessor { PointerSignalKind.SCROLL, PointerSignalKind.SCROLL_INERTIA_CANCEL, PointerSignalKind.SCALE, + PointerSignalKind.STYLUS_AUXILIARY_ACTION, PointerSignalKind.UNKNOWN }) public @interface PointerSignalKind { @@ -73,11 +74,28 @@ public class AndroidTouchProcessor { int SCROLL = 1; int SCROLL_INERTIA_CANCEL = 2; int SCALE = 3; + int STYLUS_AUXILIARY_ACTION = 4; + int UNKNOWN = 5; + } + + // Must match the PointerPreferredStylusAuxiliaryAction enum in pointer.dart. + @IntDef({ + PointerPreferredStylusAuxiliaryAction.IGNORE, + PointerPreferredStylusAuxiliaryAction.SHOW_COLOR_PALETTE, + PointerPreferredStylusAuxiliaryAction.SWITCH_ERASER, + PointerPreferredStylusAuxiliaryAction.SWITCH_PREVIOUS, + PointerPreferredStylusAuxiliaryAction.UNKNOWN + }) + public @interface PointerPreferredStylusAuxiliaryAction { + int IGNORE = 0; + int SHOW_COLOR_PALETTE = 1; + int SWITCH_ERASER = 2; + int SWITCH_PREVIOUS = 3; int UNKNOWN = 4; } // Must match the unpacking code in hooks.dart. - private static final int POINTER_DATA_FIELD_COUNT = 35; + private static final int POINTER_DATA_FIELD_COUNT = 36; @VisibleForTesting static final int BYTES_PER_FIELD = 8; // This value must match the value in framework's platform_view.dart. @@ -355,6 +373,8 @@ private void addPointerForIndex( packet.putDouble(1.0); // scale packet.putDouble(0.0); // rotation + packet.putLong(PointerPreferredStylusAuxiliaryAction.IGNORE); // preferred stylus action + if (isTrackpadPan && getPointerChangeForPanZoom(pointerChange) == PointerChange.PAN_ZOOM_END) { ongoingPans.remove(event.getPointerId(pointerIndex)); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index ced158a9eda07..27fa78f420282 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -56,7 +56,9 @@ // This is left a FlutterBinaryMessenger privately for now to give people a chance to notice the // change. Unfortunately unless you have Werror turned on, incompatible pointers as arguments are // just a warning. -@interface FlutterViewController () +@interface FlutterViewController () @property(nonatomic, readwrite, getter=isDisplayingFlutterUI) BOOL displayingFlutterUI; @property(nonatomic, assign) BOOL isHomeIndicatorHidden; @property(nonatomic, assign) BOOL isPresentingViewControllerAnimating; @@ -97,7 +99,7 @@ @interface FlutterViewController () (1); + packet->SetPointerData(/*index=*/0, pointer_data); + [_engine.get() dispatchPointerDataPacket:std::move(packet)]; +} + +- (flutter::PointerData)createAuxillaryStylusActionData API_AVAILABLE(ios(13.4)) { + flutter::PointerData pointer_data; + pointer_data.Clear(); + + switch (UIPencilInteraction.preferredTapAction) { + case UIPencilPreferredActionIgnore: + pointer_data.preferred_auxiliary_stylus_action = + flutter::PointerData::PreferredStylusAuxiliaryAction::kIgnore; + break; + case UIPencilPreferredActionShowColorPalette: + pointer_data.preferred_auxiliary_stylus_action = + flutter::PointerData::PreferredStylusAuxiliaryAction::kShowColorPalette; + break; + case UIPencilPreferredActionSwitchEraser: + pointer_data.preferred_auxiliary_stylus_action = + flutter::PointerData::PreferredStylusAuxiliaryAction::kSwitchEraser; + break; + case UIPencilPreferredActionSwitchPrevious: + pointer_data.preferred_auxiliary_stylus_action = + flutter::PointerData::PreferredStylusAuxiliaryAction::kSwitchPrevious; + break; + default: + pointer_data.preferred_auxiliary_stylus_action = + flutter::PointerData::PreferredStylusAuxiliaryAction::kUnknown; + break; + } + + pointer_data.time_stamp = [[NSProcessInfo processInfo] systemUptime] * kMicrosecondsPerSecond; + pointer_data.kind = flutter::PointerData::DeviceKind::kStylus; + pointer_data.signal_kind = flutter::PointerData::SignalKind::kStylusAuxiliaryAction; + + return pointer_data; +} + #pragma mark - Handle view resizing - (void)updateViewportMetrics { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 1461ca0640993..21110edf73fe2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -111,7 +111,8 @@ @interface FlutterEmbedderKeyResponder (Tests) @property(nonatomic, copy, readonly) FlutterSendKeyEvent sendEvent; @end -@interface FlutterViewController (Tests) +@interface FlutterViewController (Tests) +; @property(nonatomic, assign) double targetViewInsetBottom; @property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground; @@ -123,6 +124,7 @@ - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences; - (void)handlePressEvent:(FlutterUIPressProxy*)press nextAction:(void (^)())next API_AVAILABLE(ios(13.4)); - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer; +- (flutter::PointerData)createAuxillaryStylusActionData; - (void)updateViewportMetrics; - (void)onUserSettingsChanged:(NSNotification*)notification; - (void)applicationWillTerminate:(NSNotification*)notification; @@ -1474,11 +1476,6 @@ - (void)sendMessage:(id _Nullable)message reply:(FlutterReply _Nullable)callback } - (void)testValidKeyUpEvent API_AVAILABLE(ios(13.4)) { - if (@available(iOS 13.4, *)) { - // noop - } else { - return; - } FlutterEnginePartialMock* mockEngine = [[FlutterEnginePartialMock alloc] init]; mockEngine.keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]); OCMStub([mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]]) @@ -1509,12 +1506,6 @@ - (void)testValidKeyUpEvent API_AVAILABLE(ios(13.4)) { } - (void)testValidKeyDownEvent API_AVAILABLE(ios(13.4)) { - if (@available(iOS 13.4, *)) { - // noop - } else { - return; - } - FlutterEnginePartialMock* mockEngine = [[FlutterEnginePartialMock alloc] init]; mockEngine.keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]); OCMStub([mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]]) @@ -1546,11 +1537,6 @@ - (void)testValidKeyDownEvent API_AVAILABLE(ios(13.4)) { } - (void)testIgnoredKeyEvents API_AVAILABLE(ios(13.4)) { - if (@available(iOS 13.4, *)) { - // noop - } else { - return; - } id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]); OCMStub([keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]]) .andCall(self, @selector(sendMessage:reply:)); @@ -1584,12 +1570,6 @@ - (void)testIgnoredKeyEvents API_AVAILABLE(ios(13.4)) { } - (void)testPanGestureRecognizer API_AVAILABLE(ios(13.4)) { - if (@available(iOS 13.4, *)) { - // noop - } else { - return; - } - FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil]; @@ -1610,12 +1590,6 @@ - (void)testPanGestureRecognizer API_AVAILABLE(ios(13.4)) { } - (void)testMouseSupport API_AVAILABLE(ios(13.4)) { - if (@available(iOS 13.4, *)) { - // noop - } else { - return; - } - FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil]; @@ -1630,6 +1604,80 @@ - (void)testMouseSupport API_AVAILABLE(ios(13.4)) { dispatchPointerDataPacket:std::make_unique(0)]; } +- (void)testPencilSupport API_AVAILABLE(ios(13.4)) { + FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine + nibName:nil + bundle:nil]; + XCTAssertNotNil(vc); + + id mockPencilInteraction = OCMClassMock([UIPencilInteraction class]); + + OCMStub([mockPencilInteraction preferredTapAction]) + .andReturn(UIPencilPreferredActionShowColorPalette); + + // Check that the helper function is being called + FlutterViewController* viewControllerMock = OCMPartialMock(vc); + [viewControllerMock pencilInteractionDidTap:mockPencilInteraction]; + OCMVerify([viewControllerMock createAuxillaryStylusActionData]); + + [mockPencilInteraction stopMocking]; +} + +- (void)testCreateAuxillaryStylusActionData API_AVAILABLE(ios(13.4)) { + FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine + nibName:nil + bundle:nil]; + XCTAssertNotNil(vc); + + id mockPencilInteraction = OCMClassMock([UIPencilInteraction class]); + + OCMExpect([mockPencilInteraction preferredTapAction]) + .andReturn(UIPencilPreferredActionShowColorPalette); + + // Check the return value of the helper function + flutter::PointerData pointer_data = [vc createAuxillaryStylusActionData]; + + XCTAssertEqual(pointer_data.kind, flutter::PointerData::DeviceKind::kStylus); + XCTAssertEqual(pointer_data.signal_kind, + flutter::PointerData::SignalKind::kStylusAuxiliaryAction); + XCTAssertEqual(pointer_data.preferred_auxiliary_stylus_action, + flutter::PointerData::PreferredStylusAuxiliaryAction::kShowColorPalette); + + OCMExpect([mockPencilInteraction preferredTapAction]) + .andReturn(UIPencilPreferredActionSwitchEraser); + + pointer_data = [vc createAuxillaryStylusActionData]; + + XCTAssertEqual(pointer_data.kind, flutter::PointerData::DeviceKind::kStylus); + XCTAssertEqual(pointer_data.signal_kind, + flutter::PointerData::SignalKind::kStylusAuxiliaryAction); + XCTAssertEqual(pointer_data.preferred_auxiliary_stylus_action, + flutter::PointerData::PreferredStylusAuxiliaryAction::kSwitchEraser); + + OCMExpect([mockPencilInteraction preferredTapAction]) + .andReturn(UIPencilPreferredActionSwitchPrevious); + + pointer_data = [vc createAuxillaryStylusActionData]; + + XCTAssertEqual(pointer_data.kind, flutter::PointerData::DeviceKind::kStylus); + XCTAssertEqual(pointer_data.signal_kind, + flutter::PointerData::SignalKind::kStylusAuxiliaryAction); + XCTAssertEqual(pointer_data.preferred_auxiliary_stylus_action, + flutter::PointerData::PreferredStylusAuxiliaryAction::kSwitchPrevious); + + OCMExpect([mockPencilInteraction preferredTapAction]).andReturn(UIPencilPreferredActionIgnore); + + pointer_data = [vc createAuxillaryStylusActionData]; + + XCTAssertEqual(pointer_data.kind, flutter::PointerData::DeviceKind::kStylus); + XCTAssertEqual(pointer_data.signal_kind, + flutter::PointerData::SignalKind::kStylusAuxiliaryAction); + XCTAssertEqual(pointer_data.preferred_auxiliary_stylus_action, + flutter::PointerData::PreferredStylusAuxiliaryAction::kIgnore); + + [mockPencilInteraction stopMocking]; +} + - (void)testFakeEventTimeStamp { FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 46bd46a7a3f3c..31889b964ad82 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -2020,6 +2020,8 @@ inline flutter::PointerData::SignalKind ToPointerDataSignalKind( return flutter::PointerData::SignalKind::kScrollInertiaCancel; case kFlutterPointerSignalKindScale: return flutter::PointerData::SignalKind::kScale; + case kFlutterPointerSignalKindStylusAuxiliaryAction: + return flutter::PointerData::SignalKind::kStylusAuxiliaryAction; } return flutter::PointerData::SignalKind::kNone; } diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 52ca605473542..b12d41a2a3756 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -889,6 +889,7 @@ typedef enum { kFlutterPointerSignalKindScroll, kFlutterPointerSignalKindScrollInertiaCancel, kFlutterPointerSignalKindScale, + kFlutterPointerSignalKindStylusAuxiliaryAction, } FlutterPointerSignalKind; typedef struct {