Skip to content

Conversation

dkwingsmt
Copy link
Contributor

@dkwingsmt dkwingsmt commented May 17, 2023

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 to RendererBinding.renderView. In the future, we only need to modify hitTest (which we will have to do to support gestures for multi-view anyway) to make mouse tracker support multi-view.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@flutter-dashboard flutter-dashboard bot added f: gestures flutter/packages/flutter/gestures repository. framework flutter/packages/flutter repository. See also f: labels. labels May 17, 2023
@flutter-dashboard
Copy link

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.

@dkwingsmt dkwingsmt force-pushed the multiview-mouse-tracker branch from a2fb004 to e4cc017 Compare May 17, 2023 23:19
@@ -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 (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: orphan (

@Hixie
Copy link
Contributor

Hixie commented May 18, 2023

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) {
Copy link
Member

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?

Copy link
Contributor Author

@dkwingsmt dkwingsmt May 18, 2023

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) {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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,
Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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) {
Copy link
Member

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).

@dkwingsmt
Copy link
Contributor Author

@goderbauer I've reverted the changes around the cached hit test result. I'll probably submit them in a separate PR for easier discussion.

Copy link
Member

@goderbauer goderbauer left a 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,
Copy link
Member

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) {
Copy link
Member

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?

@goderbauer
Copy link
Member

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) {
Copy link
Member

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.

Copy link
Contributor Author

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);
Copy link
Member

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.

Copy link
Contributor Author

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)!;
Copy link
Member

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.

Copy link
Contributor Author

@dkwingsmt dkwingsmt Jun 8, 2023

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?

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dkwingsmt dkwingsmt added the autosubmit Merge PR when tree becomes green via auto submit App label Jun 9, 2023
@auto-submit auto-submit bot merged commit ab3c5bf into flutter:master Jun 9, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jun 9, 2023
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Jun 9, 2023
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
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 16, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 17, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 17, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App f: gestures flutter/packages/flutter/gestures repository. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants