-
Notifications
You must be signed in to change notification settings - Fork 6k
[macOS] Implement hit testing and handle platform view cursor changes #43101
Conversation
f6ab057 to
8c76f8b
Compare
cd17508 to
6b81cd5
Compare
cbracken
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.
Looking at the embedder side changes right now, but a few comments on the pointer event/cursor side first.
| for (size_t j = i + 1; j < layers_count; j++) { | ||
| FlutterLayer* overlayLayer = (FlutterLayer*)layers[j]; | ||
| if (overlayLayer->type == kFlutterLayerContentTypeBackingStore) { | ||
| auto present_info = overlayLayer->backing_store->present_info; | ||
| if (present_info != nullptr && present_info->paint_region != nullptr) { | ||
| for (size_t k = 0; k < present_info->paint_region->rects_count; k++) { | ||
| FlutterRect flutter_rect = present_info->paint_region->rects[k]; | ||
| double scale = default_base_view.layer.contentsScale; | ||
| CGRect rect = CGRectMake(flutter_rect.left / scale, flutter_rect.top / scale, | ||
| (flutter_rect.right - flutter_rect.left) / scale, | ||
| (flutter_rect.bottom - flutter_rect.top) / scale); | ||
| CGRect intersection = CGRectIntersection(rect, mutator_view.frame); | ||
| if (!CGRectIsNull(intersection)) { | ||
| intersection.origin.x -= mutator_view.frame.origin.x; | ||
| intersection.origin.y -= mutator_view.frame.origin.y; | ||
| [mutator_view addHitTestIgnoreRegion:intersection]; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
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.
Consider extracting this to its own method.
shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
Show resolved
Hide resolved
| [self replaceSelector:@selector(set) | ||
| fromClass:[NSCursor class] | ||
| withSelector:@selector(_flutterSetCursorOverride) | ||
| fromClass:[NSCursor class]]; |
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.
Avoid swizzling in all code except for tests. If it's not in the style guide already, I'll add it -- we've had this conversation many times in code reviews (including in one of mine from back in 2016 or so when I tried to get rid of FlutterAppDelegate) and in the end we always end up making the same call -- avoid it.
We don't allow swizzling AppKit classes for a few reasons:
- it can be a blocker for shipping on the App Store.
- we shouldn't apply Flutter-specific logic to non-Flutter parts of the app -- in add-to-app scenarios, developers may have just a single Flutter view in a massive app.
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.
Swizzling in general does not prevent getting into AppStore. Using private / undocumented API does, which is not the case here. This also should not interfere with the rest of application, it just prevents changing cursor until the rest of RunLoop tick when Flutter content is over native view and already handled cursor change.
The application will be working without it, but with cursor flicker when moving over Flutter contents that have native view with active NSTrackingArea underneath (i.e. NSTextField).
| if (flutterIgnoreCursorChange) { | ||
| return; | ||
| } | ||
| [self _flutterSetCursorOverride]; |
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.
For the sake of readability, at the very minimum this needs a comment pointing out that _flutterSetCursorOverride has been swizzled to use the original [NSCursor set] implementation and therefore this isn't an infinite loop. That said, see my comment on swizzling below.
|
This pull request executed golden file tests, but it has not been updated in a while (20+ days). Test results from Gold expire after as many days, so this pull request will need to be updated with a fresh commit in order to get results from Gold. |
5f86374 to
8c2304d
Compare
|
@knopp Are you still planning on working on this? If not, could you either close it or mark it as draft so we can get it off the PR backlog? (Desktop Triage) |
|
This is pending another round of review from me, but been stuck working on two internal customer issues, I'll try to get back to this soon. Apologies for the delay. |
8c2304d to
6bc5add
Compare
|
Converting this to Draft, as @cbracken is still looking at alternatives. |
|
Spoke to @cbracken and he says this is still on his radar. |
| if (present_info) { | ||
| for (size_t j = 0; j < present_info->paint_region->rects_count; j++) { | ||
| rects.push_back(present_info->paint_region->rects[j]); | ||
| } |
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.
Consider pre-reserving size of rects, and using std::copy which might have better performance:
rects.reserve(present_info->paint_region->rects_count);
std::copy(present_info->paint_region->rects,
present_info->paint_region->rects + present_info->paint_region->rects_count,
std::back_inserter(rects));
cbracken
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.
…146524) flutter/engine@5a824e2...5dca50d 2024-04-09 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Delete engine v1 android embedding (#51229)" (flutter/engine#51996) 2024-04-09 [email protected] Delete engine v1 android embedding (flutter/engine#51229) 2024-04-09 [email protected] better output from engine layer unit test failures (flutter/engine#51975) 2024-04-09 [email protected] Revert "Roll Dart SDK from 78174b41ab0f to 7a5e410f982e (1 revision) (#51980)" (flutter/engine#51990) 2024-04-09 [email protected] [skwasm] Reify the SkPicture pointer as the right type. (flutter/engine#51991) 2024-04-09 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Roll Dart SDK from 7a5e410f982e to db99af14c4bc (1 revision) (#51986)" (flutter/engine#51989) 2024-04-09 [email protected] Move the Dart SDK to //flutter/third_party/dart (flutter/engine#51917) 2024-04-09 [email protected] [macOS] Implement hit testing and handle platform view cursor changes (flutter/engine#43101) 2024-04-09 [email protected] Roll Dart SDK from 7a5e410f982e to db99af14c4bc (1 revision) (flutter/engine#51986) 2024-04-09 [email protected] Roll Dart SDK from 78174b41ab0f to 7a5e410f982e (1 revision) (flutter/engine#51980) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-engine-flutter-autoroll Please CC [email protected],[email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
This fixes an edge case that I've overlooked in #43101. It is possible to configure `FlutterViewController` tracking area with `FlutterMouseTrackingModeAlways` option, in which case the tracking area will call `[FlutterView cursorUpdate:]` unconditionally even if the mouse cursor is over platform view and the platform view has already handled the mouse update. The solution is to prevent the `FlutterView` from updating the cursor when the cursor is over a platform view. This can be accomplished by doing a hitTest inside `[FlutterView cursorUpdate:]`. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I signed the [CLA]. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style [testing the engine]: https://github.com/flutter/flutter/wiki/Testing-the-engine [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat
…lutter#146524) flutter/engine@5a824e2...5dca50d 2024-04-09 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Delete engine v1 android embedding (flutter#51229)" (flutter/engine#51996) 2024-04-09 [email protected] Delete engine v1 android embedding (flutter/engine#51229) 2024-04-09 [email protected] better output from engine layer unit test failures (flutter/engine#51975) 2024-04-09 [email protected] Revert "Roll Dart SDK from 78174b41ab0f to 7a5e410f982e (1 revision) (flutter#51980)" (flutter/engine#51990) 2024-04-09 [email protected] [skwasm] Reify the SkPicture pointer as the right type. (flutter/engine#51991) 2024-04-09 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Roll Dart SDK from 7a5e410f982e to db99af14c4bc (1 revision) (flutter#51986)" (flutter/engine#51989) 2024-04-09 [email protected] Move the Dart SDK to //flutter/third_party/dart (flutter/engine#51917) 2024-04-09 [email protected] [macOS] Implement hit testing and handle platform view cursor changes (flutter/engine#43101) 2024-04-09 [email protected] Roll Dart SDK from 7a5e410f982e to db99af14c4bc (1 revision) (flutter/engine#51986) 2024-04-09 [email protected] Roll Dart SDK from 78174b41ab0f to 7a5e410f982e (1 revision) (flutter/engine#51980) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-engine-flutter-autoroll Please CC [email protected],[email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md

Fixes flutter/flutter#129085
Changes to
FlutterMouseCursorPluginnonecursor is not handled by hiding the cursor anymore. That was too big of a hammer as it also affects other views (and platform views). Instead empty cursor is created from emptyNSImage.Cursor plugin now notifies the engine when cursor has changed. The engine forwards the change to last
FlutterViewthat handled mouse event. This is necessary because on occasionFlutterViewneeds to be able to restore cursor without framework being involved.Preventing PlatformView from changing cursor when it is obscured by Flutter Content.
Generally in Cocoa cursor changes are done as response to
mouseMovedevent, which is driven by aNSTrackingArea. The issue here is that this is not affected by hit testing and tracking areas form a hierarchy parallel to view hierarchy and are not affected by being obscured by another view (or tracking area). This means that platform view will receivemouseMovedevent even when is obscured by Flutter content. To work around this, the mutator view puts a tracking area above platform view, which means it gets the mouseMove event first, and when it decides that mouse is over Flutter content, it will prevent platform view from changing the cursor for the rest of RunLoop turn (seeNSCursor+IgnoreChange).Actual hit testing
This part is rather straightforward, the area where FlutterContent obscures mutator view is provided to the mutator view, which will return
nilfromhitTest:when the point is in the obscured area.Example of hit testing
flutter-hit-test1.mov
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.