Support right-clicking on iPadOS (flutter/engine#27019)

This commit is contained in:
Callum Moffat 2021-07-02 16:52:24 -04:00 committed by GitHub
parent 07cda5195c
commit 764f2e1137
8 changed files with 114 additions and 15 deletions

View File

@ -18,3 +18,4 @@ shoryukenn <naifu.guan@gmail.com>
SOTEC GmbH & Co. KG <sotec-contributors@sotec.eu>
Hidenori Matsubayashi <Hidenori.Matsubayashi@sony.com>
Sarbagya Dhaubanjar <mail@sarbagyastha.com.np>
Callum Moffat <callum@moffatman.com>

View File

@ -814,7 +814,8 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
// in the status bar area are available to framework code. The change type (optional) of the faked
// touch is specified in the second argument.
- (void)dispatchTouches:(NSSet*)touches
pointerDataChangeOverride:(flutter::PointerData::Change*)overridden_change {
pointerDataChangeOverride:(flutter::PointerData::Change*)overridden_change
event:(UIEvent*)event {
if (!_engine) {
return;
}
@ -925,6 +926,17 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
}
if (@available(iOS 13.4, *)) {
if (event != nullptr) {
pointer_data.buttons = (((event.buttonMask & UIEventButtonMaskPrimary) > 0)
? flutter::PointerButtonMouse::kPointerButtonMousePrimary
: 0) |
(((event.buttonMask & UIEventButtonMaskSecondary) > 0)
? flutter::PointerButtonMouse::kPointerButtonMouseSecondary
: 0);
}
}
packet->SetPointerData(pointer_index++, pointer_data);
}
@ -932,24 +944,24 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
}
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
[self dispatchTouches:touches pointerDataChangeOverride:nullptr];
[self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
[self dispatchTouches:touches pointerDataChangeOverride:nullptr];
[self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
}
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
[self dispatchTouches:touches pointerDataChangeOverride:nullptr];
[self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
}
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
[self dispatchTouches:touches pointerDataChangeOverride:nullptr];
[self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
}
- (void)forceTouchesCancelled:(NSSet*)touches {
flutter::PointerData::Change cancel = flutter::PointerData::Change::kCancel;
[self dispatchTouches:touches pointerDataChangeOverride:&cancel];
[self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
}
#pragma mark - Handle view resizing

View File

@ -56,6 +56,7 @@
6816DBA42318358200A51400 /* GoldenTestManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 6816DBA32318358200A51400 /* GoldenTestManager.m */; };
68A5B63423EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68A5B63323EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m */; };
68D4017D2564859300ECD91A /* ContinuousTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = 68D4017C2564859300ECD91A /* ContinuousTexture.m */; };
F26F15B8268B6B5600EC54D3 /* iPadGestureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F26F15B7268B6B5500EC54D3 /* iPadGestureTests.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -172,6 +173,7 @@
68A5B63323EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlatformViewGestureRecognizerTests.m; sourceTree = "<group>"; };
68D4017B2564859300ECD91A /* ContinuousTexture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContinuousTexture.h; sourceTree = "<group>"; };
68D4017C2564859300ECD91A /* ContinuousTexture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContinuousTexture.m; sourceTree = "<group>"; };
F26F15B7268B6B5500EC54D3 /* iPadGestureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iPadGestureTests.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -295,6 +297,7 @@
246A6610252E693A00EAB0F3 /* RenderingSelectionTest.m */,
0DDEBC87258830B40065D0E8 /* SpawnEngineTest.h */,
0DDEBC88258830B40065D0E8 /* SpawnEngineTest.m */,
F26F15B7268B6B5500EC54D3 /* iPadGestureTests.m */,
);
path = ScenariosUITests;
sourceTree = "<group>";
@ -490,6 +493,7 @@
6816DBA42318358200A51400 /* GoldenTestManager.m in Sources */,
248D76EF22E388380012F0C1 /* PlatformViewUITests.m in Sources */,
0D8470A4240F0B1F0030B565 /* StatusBarTest.m in Sources */,
F26F15B8268B6B5600EC54D3 /* iPadGestureTests.m in Sources */,
246A6611252E693A00EAB0F3 /* RenderingSelectionTest.m in Sources */,
4F06F1B32473296E000AF246 /* LocalizationInitializationTest.m in Sources */,
0A42BFB42447E179007E212E /* TextSemanticsFocusTest.m in Sources */,

View File

@ -57,6 +57,7 @@
@"--platform-view-with-continuous-texture" : @"platform_view_with_continuous_texture",
@"--bogus-font-text" : @"bogus_font_text",
@"--spawn-engine-works" : @"spawn_engine_works",
@"--pointer-events" : @"pointer_events",
};
__block NSString* flutterViewControllerTestName = nil;
[launchArgsMap
@ -109,6 +110,7 @@
FlutterEngine* engine = [self engineForTest:scenarioIdentifier];
FlutterViewController* flutterViewController =
[self flutterViewControllerForTest:scenarioIdentifier withEngine:engine];
flutterViewController.view.accessibilityIdentifier = @"flutter_view";
[engine.binaryMessenger
setMessageHandlerOnChannel:@"waiting_for_status"

View File

@ -30,10 +30,13 @@
[[self.application.statusBars firstMatch] tap];
}
XCUIElement* addTextField = self.application.textFields[@"PointerChange.add"];
XCUIElement* addTextField = self.application.textFields[@"PointerChange.add:0"];
BOOL exists = [addTextField waitForExistenceWithTimeout:1];
XCTAssertTrue(exists, @"");
XCUIElement* upTextField = self.application.textFields[@"PointerChange.up"];
XCUIElement* downTextField = self.application.textFields[@"PointerChange.down:0"];
exists = [downTextField waitForExistenceWithTimeout:1];
XCTAssertTrue(exists, @"");
XCUIElement* upTextField = self.application.textFields[@"PointerChange.up:0"];
exists = [upTextField waitForExistenceWithTimeout:1];
XCTAssertTrue(exists, @"");
}

View File

@ -0,0 +1,66 @@
// Copyright 2020 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.
#import <XCTest/XCTest.h>
static const NSInteger kSecondsToWaitForFlutterView = 30;
@interface iPadGestureTests : XCTestCase
@end
@implementation iPadGestureTests
- (void)setUp {
[super setUp];
self.continueAfterFailure = NO;
}
#ifdef __IPHONE_15_0
- (void)testPointerButtons {
if (@available(iOS 15, *)) {
XCTSkipUnless([XCUIDevice.sharedDevice supportsPointerInteraction],
"Device does not support pointer interaction");
XCUIApplication* app = [[XCUIApplication alloc] init];
app.launchArguments = @[ @"--pointer-events" ];
[app launch];
NSPredicate* predicateToFindFlutterView =
[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject,
NSDictionary<NSString*, id>* _Nullable bindings) {
XCUIElement* element = evaluatedObject;
return [element.identifier hasPrefix:@"flutter_view"];
}];
XCUIElement* flutterView = [[app descendantsMatchingType:XCUIElementTypeAny]
elementMatchingPredicate:predicateToFindFlutterView];
if (![flutterView waitForExistenceWithTimeout:kSecondsToWaitForFlutterView]) {
NSLog(@"%@", app.debugDescription);
XCTFail(@"Failed due to not able to find any flutterView with %@ seconds",
@(kSecondsToWaitForFlutterView));
}
XCTAssertNotNil(flutterView);
[flutterView tap];
// Initial add event should have buttons = 0
XCTAssertTrue([app.textFields[@"PointerChange.add:0"] waitForExistenceWithTimeout:1],
@"PointerChange.add event did not occur");
// Normal tap should have buttons = 0, the flutter framework will ensure it has buttons = 1
XCTAssertTrue([app.textFields[@"PointerChange.down:0"] waitForExistenceWithTimeout:1],
@"PointerChange.down event did not occur for a normal tap");
XCTAssertTrue([app.textFields[@"PointerChange.up:0"] waitForExistenceWithTimeout:1],
@"PointerChange.up event did not occur for a normal tap");
[flutterView rightClick];
// Since each touch is its own device, we can't distinguish the other add event(s)
// Right click should have buttons = 2
XCTAssertTrue([app.textFields[@"PointerChange.down:2"] waitForExistenceWithTimeout:1],
@"PointerChange.down event did not occur for a right-click");
XCTAssertTrue([app.textFields[@"PointerChange.up:2"] waitForExistenceWithTimeout:1],
@"PointerChange.up event did not occur for a right-click");
NSLog(@"DebugDescriptionX: %@", app.debugDescription);
}
}
#endif
@end

View File

@ -48,6 +48,7 @@ Map<String, ScenarioFactory> _scenarios = <String, ScenarioFactory>{
'platform_view_with_continuous_texture': () => PlatformViewWithContinuousTexture(PlatformDispatcher.instance, 'Platform View', id: _viewId++),
'bogus_font_text': () => BogusFontText(PlatformDispatcher.instance),
'spawn_engine_works' : () => BogusFontText(PlatformDispatcher.instance),
'pointer_events': () => TouchesScenario(PlatformDispatcher.instance),
};
Map<String, dynamic> _currentScenarioParams = <String, dynamic>{};

View File

@ -14,14 +14,24 @@ class TouchesScenario extends Scenario {
/// Constructor for `TouchesScenario`.
TouchesScenario(PlatformDispatcher dispatcher) : super(dispatcher);
@override
void onBeginFrame(Duration duration) {
// It is necessary to render frames for touch events to work properly on iOS
final Scene scene = SceneBuilder().build();
window.render(scene);
scene.dispose();
}
@override
void onPointerDataPacket(PointerDataPacket packet) {
sendJsonMessage(
dispatcher: dispatcher,
channel: 'display_data',
json: <String, dynamic>{
'data': packet.data[0].change.toString(),
},
);
for (final PointerData datum in packet.data) {
sendJsonMessage(
dispatcher: dispatcher,
channel: 'display_data',
json: <String, dynamic>{
'data': datum.change.toString() + ':' + datum.buttons.toString(),
},
);
}
}
}