-
Notifications
You must be signed in to change notification settings - Fork 6k
Remove iPadOS mouse pointer if no longer connected #28319
Conversation
aacdf5f to
95c3bb3
Compare
chinmaygarde
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
Outdated
Show resolved
Hide resolved
shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
Outdated
Show resolved
Hide resolved
testing/scenario_app/ios/Scenarios/ScenariosUITests/iPadGestureTests.m
Outdated
Show resolved
Hide resolved
95c3bb3 to
fc98152
Compare
|
Can you file an issue and link it in the description please? That helps other people that run into this problem discover it. What do you think about an approach that would fire an NSTimer to poll for the presence of the device every 500ms one a device is added and remove it when it's no longer present, instead of relying on the next touch event to clear it out? |
I thought about it, and initially didn't like polling approach. And also, iOS removes the cursor/focus environment when the user hasn't moved the mouse for only a few seconds (I measured it at ~3.5 seconds). I suppose it's iOS policy that the cursor should be removed when inactive, so maybe it should be followed. If the user was actively hovering something when the cursor went inactive, though, it might be unexpected. They would see the cursor fade out, then ~0-500ms later, the hover stop. In one of my Flutter apps, I have some content that appears on hover, sometimes I leave the mouse inactive while I read the content. If the Flutter cursor was also removed, it would be annoying, but I suppose it's rare to use that desktop UI paradigm on the iPad. Mostly on iPad hover is just used to do the fancy cursor effects. |
fc98152 to
6b9240b
Compare
|
Couldn't we mimic that behavior though, right? Anytime there is cursor activity we reset the timer that is set for ~3.5 seconds. That way we are matching the behavior of UIKit apps, no? |
|
It's a good idea, I implemented it but I realized that the UIFocusSystemCheck doesn't care about whether the cursor is active, it's just if the mouse is connected or not. I think I was confused with some other APIs I was trying out to check the pointer status, there isn't a great official one. But this means I can implement your original suggestion, check every 500ms if the mouse is plugged in. |
4e7429f to
8191524
Compare
|
Just a drive by comment about "but then disconnect that device, the pointer will stay in the framework at its previous position". Isn't it the case that devices when disconnected broadcast a cancelled pointer event? The framework should be able to detect that instead of there being a need to poll for the active pointers. |
|
@chinmaygarde |
|
If @gaaclarke is busy, I can take a stab at reviewing this. It looks like there is consensus on this approach being the right way forward. |
chinmaygarde
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The guide for tests discourages timeouts but I am not able to provide better guidance on how to test this. LGTM.
|
I found that this is currently not testable, the pointerInteraction here does not get triggered by synthetic hover events in recent versions of Xcode (it did at one point work during the beta cycle for Xcode 13). I discovered that when using UIHoverGestureRecognizer, it does get triggered properly. If we use that class instead of PointerInteraction we will also get a state-change when the pointer is disconnected, so it's overall a better solution for this issue in general. I will update this PR with that change, let's not merge this. |
de5e6a0 to
e5ade78
Compare
| pointer_data.change = flutter::PointerData::Change::kRemove; | ||
| break; | ||
| default: | ||
| FML_LOG(ERROR) << "Unhandled hover phase: " << _hoverGestureRecognizer.get().state; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This log is not user actionable. Let's remove it. Also, flutter::PointerData::Change::kCancel seems more apt here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kCancel is meant for when the pointer is down, since this pointer is never down it will cause failed assertions during processing.
Since the remaining unhandled and unexpected states are all a kind of semi-recognized I thought that kHover was the closest choice. And it's the only one that will not cause a fatal double add or double remove.
a85eb94 to
2b26b23
Compare
jmagman
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The view controller code looks good. Had some nits about the test, and suggestions for making what it is expecting clearer.
I don't have a bluetooth mouse to test this with at the moment.
testing/scenario_app/ios/Scenarios/ScenariosUITests/iPadGestureTests.m
Outdated
Show resolved
Hide resolved
testing/scenario_app/ios/Scenarios/ScenariosUITests/iPadGestureTests.m
Outdated
Show resolved
Hide resolved
testing/scenario_app/ios/Scenarios/ScenariosUITests/iPadGestureTests.m
Outdated
Show resolved
Hide resolved
testing/scenario_app/ios/Scenarios/ScenariosUITests/iPadGestureTests.m
Outdated
Show resolved
Hide resolved
testing/scenario_app/ios/Scenarios/ScenariosUITests/iPadGestureTests.m
Outdated
Show resolved
Hide resolved
testing/scenario_app/ios/Scenarios/ScenariosUITests/iPadGestureTests.m
Outdated
Show resolved
Hide resolved
testing/scenario_app/ios/Scenarios/ScenariosUITests/iPadGestureTests.m
Outdated
Show resolved
Hide resolved
testing/scenario_app/ios/Scenarios/ScenariosUITests/iPadGestureTests.m
Outdated
Show resolved
Hide resolved
| NSMutableDictionary* messages = [NSMutableDictionary dictionary]; | ||
| for (XCUIElement* element in [app.textFields allElementsBoundByIndex]) { | ||
| NSString* rawMessage = element.value; | ||
| NSUInteger commaIndex = [rawMessage rangeOfString:@","].location; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section is hard to understand, what is it doing, exactly? Does -componentsSeparatedByString make it easier to read?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just need the first component, I don't want to split the whole string by commas since there are more commas later.
| @"PointerChange.up event did not occur for a right-click"); | ||
| int rightClickAddedSeqNo, rightClickDownSeqNo, rightClickUpSeqNo; | ||
| if ([messages objectForKey:@"PointerChange.add,device=2,buttons=0"] == nil) { | ||
| // Sometimes the tap pointer has the same device as the right-click (the UITouch is reused) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test generally may be easier to read as a series of expectations, with ordering either enforced or not depending on what you're checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I observed a lot of different permutations of event order, even though this is somewhat convoluted I don't think the alternative is better.
b1a8771 to
2962099
Compare
0a210ec to
821c4b5
Compare
|
@jmagman friendly ping. I looked through your feedback and their response, this looks good to me now. |
jmagman
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
| [app.textFields[@"1,PointerChange.hover,device=0,buttons=0"] waitForExistenceWithTimeout:1], | ||
| @"PointerChange.hover event did not occur for a hover"); | ||
| // The number of hover events fired is not always the same | ||
| NSInteger lastHoverSequenceNumber = -1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initializing to NSNotFound and checking (lastHoverSequenceNumber == NSNotFound) || (messageSequenceNumber > lastHoverSequenceNumber) is probably more idiomatic than -1, but this is fine.
On iPadOS, if you are using a flutter application with a mouse or trackpad connected, but then disconnect that device, the pointer will stay in the framework at its previous position. So any Widget which interacts on PointerEnter/PointerExit will behave strangely. with no way to resolve it.
There is a way to query whether the pointer should be shown any more, but no way to get a callback from the system when this happens. So I perform the check when the user touches the screen, this should still provide a much better experience than before.
The test doesn't pass on the most recent Xcode 13 betas, the hover selector is broken in the simulator. I filed radar FB9427343 so hopefully that will be addressed. But CI is not running the iPadGestureTests yet anyways.
Fixes flutter/flutter#89878