-
Notifications
You must be signed in to change notification settings - Fork 29.1k
Remove single view assumption from MouseTracker, and unify its hit testing code flow #127060
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove single view assumption from MouseTracker, and unify its hit testing code flow #127060
Conversation
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!). If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
a2fb004
to
e4cc017
Compare
@@ -334,8 +334,17 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H | |||
|
|||
/// State for all pointers which are currently down. | |||
/// | |||
/// The state of hovering pointers is not tracked because that would require | |||
/// hit-testing on every frame. | |||
/// This map caches the hit test result done when the pointer starts tapping ( |
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.
nit: orphan (
test-exempt: code refactor with no semantic change |
@@ -315,18 +315,21 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture | |||
@visibleForTesting | |||
void initMouseTracker([MouseTracker? tracker]) { | |||
_mouseTracker?.dispose(); | |||
_mouseTracker = tracker ?? MouseTracker(); | |||
_mouseTracker = tracker ?? MouseTracker((Offset position) { |
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.
So, the plan is that for multi view we will extend the signature of the MouseTrackerRenderViewHitTest
here and have it take viewId as an argument as well?
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.
Yes. And hitTest
probably needs a new viewId
parameter as well, as in your prototype framework PR (or in other less breaking ways.)
@@ -380,13 +404,6 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H | |||
}()); | |||
} else if (event is PointerUpEvent || event is PointerCancelEvent || event is PointerPanZoomEndEvent) { | |||
hitTestResult = _hitTests.remove(event.pointer); | |||
} else if (event.down || event is PointerPanZoomUpdateEvent) { |
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 my own understanding: In the old code we were checking event.down
to determine whether to use a cached hitTestResult, in the new code we are using event is PointerMoveEvent
- why this change? Are they equivalent?
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 agree. But the current state is:
- At the outside (
_handlePointerEventImmediately
, here) the code is checking "is down or is PanZoomUpdateEvent, after eliminating all the other classes". - At the inside, the code is checking "event is MoveEvent" (we definitely want to include PanZoomUpdateEvent as well).
- We definitely would like them to agree with each other.
So first, to answer your question directly (which is a valid question), the event when down
is true after eliminating PointerDownEvent
can only mean PointerMoveEvent
. Second, even if it's not, we'd like the two conditions to agree with each other.
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.
Second, even if it's not, we'd like the two conditions to agree with each other.
Makes sense, but this was now reverted?
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.
Yeah, I think the fix might be better off in a separate PR.
// When the button is pressed, normal hit test uses a cached result, but | ||
// MouseTracker requires that the hit test is re-executed to update the | ||
// hovering events. | ||
useCachedHitTestResult(event) ? null : hitTestResult, |
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 is kinda funny: when useCachedHitTestResult
returns true, we DON'T use the cached hit test result... wondering if we maybe need a better name for useCachedHitTestResult
.
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.
Also wondering: Why is it not just up to the mouseTracker to decide whether it wants to re-use the cached hitTestResult or create a new one based on the event it receives? It basically already does this for PointerRemovedEvent
where the provided hitTestResult is silently replaced. Why not do this for all events and basically document on updateWithEvent
that if available it takes the hitTestResult performed when the pointer associated with the provided event first went down. This is an optimization; the MouseTracker may ignore that result if necessary and perform its own hit testing.
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 don't think it's up to the MouseTracker
to decide, because the MouseTracker
has no idea how the hit test result it received was generated.
I have a better idea: let the GestureBinding
provide a method that always return the uncached hit test. Let me do this right now.
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 don't think it's up to the MouseTracker to decide, because the MouseTracker has no idea how the hit test result it received was generated.
I was thinking, we could just define that the MouseTracker always wants the hitTestResult that was obtained when the pointer associated with the event first went down (this is the only result we cache in _hitTests
anyways, right?), if such a result is available, or null, if it is not. If you pass in any other hitTestResult, you're in violation of the API contract. Then, the mouse tracker can decide if it wants to use that cached hitTestResult or not, depending on the current event type.
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 MouseTracker
just doesn't want any cached result. It always needs the hit test result performed at this frame. The current plan (before the revert) is that the MouseTracker
always receive the fresh result if available or null if not.
/// Tapping (and pan zooming) uses the same hit test result throughout the | ||
/// tap sequence, but hovering needs fresh hit tests every frame. | ||
@protected | ||
bool useCachedHitTestResult(PointerEvent event) { |
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.
It took me a good amount of time to understand the purpose and semantics of useCachedHitTestResult
. Among other things, it's kinda confusing that - as the comment below admits - it's not actually telling you about all caching instances. It seems to be very much tailored to the needs of the MouseTracker instead of being a general purpose API. Another reason for maybe considering to have the MouseTracker internally decide whether to reuse the hitTestResult from the pointer down (see my other comment on this).
@goderbauer I've reverted the changes around the cached hit test result. I'll probably submit them in a separate PR for easier discussion. |
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.
Overall this LGTM. Let's talk tonight in our sync briefly whether we want to submit this to master (and break some of this again when we add the viewID) or if we should submit this to the branch first.
// When the button is pressed, normal hit test uses a cached result, but | ||
// MouseTracker requires that the hit test is re-executed to update the | ||
// hovering events. | ||
useCachedHitTestResult(event) ? null : hitTestResult, |
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 don't think it's up to the MouseTracker to decide, because the MouseTracker has no idea how the hit test result it received was generated.
I was thinking, we could just define that the MouseTracker always wants the hitTestResult that was obtained when the pointer associated with the event first went down (this is the only result we cache in _hitTests
anyways, right?), if such a result is available, or null, if it is not. If you pass in any other hitTestResult, you're in violation of the API contract. Then, the mouse tracker can decide if it wants to use that cached hitTestResult or not, depending on the current event type.
@@ -380,13 +404,6 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H | |||
}()); | |||
} else if (event is PointerUpEvent || event is PointerCancelEvent || event is PointerPanZoomEndEvent) { | |||
hitTestResult = _hitTests.remove(event.pointer); | |||
} else if (event.down || event is PointerPanZoomUpdateEvent) { |
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.
Second, even if it's not, we'd like the two conditions to agree with each other.
Makes sense, but this was now reverted?
Looks like the checks are unhappy. |
@@ -315,18 +331,21 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture | |||
@visibleForTesting | |||
void initMouseTracker([MouseTracker? tracker]) { | |||
_mouseTracker?.dispose(); | |||
_mouseTracker = tracker ?? MouseTracker(); | |||
_mouseTracker = tracker ?? MouseTracker((int viewId, Offset position) { |
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.
nit: for consistency lets switch the parameter order around since hitTestInView puts position first followed by viewId.
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.
Done.
/// | ||
/// It is used by the [MouseTracker] to fetch annotations for the mouse | ||
/// position. | ||
typedef MouseDetectorAnnotationFinder = HitTestResult Function(Offset offset); | ||
typedef MouseTrackerRenderViewHitTest = HitTestResult Function(int viewId, Offset offset); |
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.
nit: let's remove the "RenderView" from this name. Technically, the MouseTracker doesn't care what the hit test is performed on. It only wants to know what is hit at a particular offset in a particular view. Whether that view is backed by a RenderView is not relevant.
Maybe just call this MouseTrackerHitTest
?
Similarly, "render view" should be removed from the doc and and the property below.
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.
Done.
@@ -520,8 +539,9 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture | |||
|
|||
@override | |||
void hitTestInView(HitTestResult result, Offset position, int viewId) { | |||
assert(viewId == renderView.flutterView.viewId); | |||
renderView.hitTest(result, position: position); | |||
final RenderView targetView = _viewIdToRenderView(viewId)!; |
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.
Can you revert this back to just checking that the provided viewId matches the FlutterView associated with the RenderView? It's not necessary in this change to make an assumption about how the RenderView will be retrieved based on the viewId (the actual implementation will actually use a map here instead of a function) - and until that's implemented it just makes this code look like it implements a functionality that's just not there yet.
You could move the TODO from above to here, though, since this is going to be the assert that fires when we add multi view support and forget to update that. Having a TODO with some specific instructions here would be helpful then.
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.
It's either extracting it to a method _viewIdToRenderView
or inlining the function here, because now that hitTestInView
has a viewId
, I have to check whether this ID is the implicit view ID, and skip the hit testing if the view is not the implicit view. I'm leaning to keeping the current structure, and when you implement it with a map you can change the line to final RenderView targetView = _viewIdToRenderView[viewId!];
. What do you think?
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.
There is currently no way this can give you anything but that ID, right? So I would argue that just firing an assert here is the right thing to do if the viewID is not the expected (and only valid) one. In the current implementation it would actually give you a null-dereferencing error on line 542 if we were to receive an invalid ID, which I think is worse then firing an assert clearly stating what the current expected behavior is.
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.
You're right! I'll do it.
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
…s hit testing code flow (flutter/flutter#127060)
flutter/flutter@6e254a3...da127f1 2023-06-09 hans.muller@gmail.com Updated material button theme tests for Material3 (flutter/flutter#128543) 2023-06-09 engine-flutter-autoroll@skia.org Roll Flutter Engine from cb93477008d6 to 93afba901b3b (2 revisions) (flutter/flutter#128573) 2023-06-09 6655696+guidezpl@users.noreply.github.com Improve defaults generation with logging, stats, and token validation (flutter/flutter#128244) 2023-06-09 whesse@google.com [testing] Make the FLUTTER_STORAGE_BASE_URL warning non-fatal (flutter/flutter#128335) 2023-06-09 danny@tuppeny.com [flutter_tools] [DAP] Don't try to restart/reload if app hasn't started yet (flutter/flutter#128267) 2023-06-09 engine-flutter-autoroll@skia.org Roll Flutter Engine from 8f9e608d39ab to cb93477008d6 (3 revisions) (flutter/flutter#128568) 2023-06-09 tessertaha@gmail.com Replace `MaterialButton` from test classes (flutter/flutter#128466) 2023-06-09 tessertaha@gmail.com Fix `showBottomSheet` doesn't remove scrim when draggable sheet is dismissed (flutter/flutter#128455) 2023-06-09 engine-flutter-autoroll@skia.org Manual roll Flutter Engine from a5f7d5d75ff2 to 8f9e608d39ab (31 revisions) (flutter/flutter#128554) 2023-06-09 ychris@google.com Revert "test owners: cyanglaz -> vashworth" (flutter/flutter#128462) 2023-06-09 43054281+camsim99@users.noreply.github.com [Android] Bump integration tests using `compileSdkVersion` 31 to 33 (flutter/flutter#128072) 2023-06-09 dkwingsmt@users.noreply.github.com Remove single view assumption from MouseTracker, and unify its hit testing code flow (flutter/flutter#127060) 2023-06-09 christopherfujino@gmail.com [flutter_tools] Precache after channel switch (flutter/flutter#118129) 2023-06-08 leigha.jarett@gmail.com Adding migration guide for Material 3 colors (flutter/flutter#128429) 2023-06-08 gspencergoog@users.noreply.github.com Add `AppLifecycleListener`, with support for application exit handling (flutter/flutter#123274) 2023-06-08 thkim1011@users.noreply.github.com Sliver Main Axis Group (flutter/flutter#126596) 2023-06-08 31859944+LongCatIsLooong@users.noreply.github.com Reduce `_DoubleClampVisitor` false positives (flutter/flutter#128539) 2023-06-08 leigha.jarett@gmail.com Advise developers to use OverflowBar instead of ButtonBar (flutter/flutter#128437) 2023-06-08 jacksongardner@google.com Reland "Migrate benchmarks to package:web" (flutter/flutter#128266) 2023-06-08 53684884+mhbdev@users.noreply.github.com Navigator.pop before PopupMenuItem onTap call (flutter/flutter#127446) 2023-06-08 leroux_bruno@yahoo.fr Fix navigation rail with long labels misplaced highlights (flutter/flutter#128324) 2023-06-08 tessertaha@gmail.com Update `chip.dart` to use set of `MaterialState` (flutter/flutter#128507) 2023-06-08 jcollins@google.com Update flutter to dartdoc 6.3.0 and hide Icons implementation from doc pages (flutter/flutter#128442) 2023-06-08 31859944+LongCatIsLooong@users.noreply.github.com Disable blinking cursor when `EditableText.showCursor` is false (flutter/flutter#127562) 2023-06-08 41930132+hellohuanlin@users.noreply.github.com [floating_cursor_selection]add more comments on the tricky part (flutter/flutter#127227) 2023-06-08 goderbauer@google.com Move RenderObjectElement.updateChildren to Element (flutter/flutter#128458) 2023-06-08 goderbauer@google.com Fix PointerEventConverter doc (flutter/flutter#128452) 2023-06-08 engine-flutter-autoroll@skia.org Roll Packages from a84b2c2 to e13b8c4 (9 revisions) (flutter/flutter#128508) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC rmistry@google.com,stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
…s hit testing code flow (flutter/flutter#127060)
…s hit testing code flow (flutter/flutter#127060)
…s hit testing code flow (flutter/flutter#127060)
…s hit testing code flow (flutter/flutter#127060)
This is a refactor to make
MouseTracker
use the same callback for both kinds of device update. Instead of using two different callbacks for the two device updating methods,MouseTracker
now receives a hit testing callback at construction, which is the same hit testing method as the one used for other gestures.This PR not only makes the code cleaner, but also removes the single view assumption from
MouseTracker
, whose code no longer refers toRendererBinding.renderView
. In the future, we only need to modifyhitTest
(which we will have to do to support gestures for multi-view anyway) to make mouse tracker support multi-view.Pre-launch Checklist
///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.