Skip to content

Add key to mouse annotations#46034

Closed
dkwingsmt wants to merge 35 commits intoflutter:masterfrom
dkwingsmt:mouse-annotation-key
Closed

Add key to mouse annotations#46034
dkwingsmt wants to merge 35 commits intoflutter:masterfrom
dkwingsmt:mouse-annotation-key

Conversation

@dkwingsmt
Copy link
Contributor

@dkwingsmt dkwingsmt commented Dec 3, 2019

Description

This PR adds keys to MouseTrackerAnnotation. When determining mouse enter and exit, annotations with equal keys will be considered the same.

This is a prerequisite to mouse cursors, because render objects want to change properties of MouseTrackerAnnotations, which is a const class. Without a key to track the inherited presence, there is no way to switch properties without triggering onExit and onEnter. The first design of mouse cursor had to use a function to resolve this, which seems odd, as pointed out by https://github.com/flutter/flutter/pull/42797/files#r342219907.

This PR also

  • Moves the definitions of UniqueKey and ObjectKey to the foundation package so that it's available at lower-layer packages.
  • Changes MouseTracker.{attach,detach}Annotation to accepting a key instead of annotations.
  • Removes _MouseRegionElement since they're no longer needed.
  • Improves the performance of the annotation dispatching algorithm.
  • Fixes an issue where changing MouseRegion.opaque doesn't work.

Related Issues

N/A

Tests

I added the following tests:

  • annotations with the same key should inherit presence
  • Changing MouseRegion's callbacks is effective and doesn't repaint
  • Changing MouseRegion.opaque is effective and repaints

Checklist

Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]). This will ensure a smooth and quick review process.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Did any tests fail when you ran them? Please read Handling breaking changes.

@fluttergithubbot fluttergithubbot added the framework flutter/packages/flutter repository. See also f: labels. label Dec 3, 2019
@dkwingsmt dkwingsmt added a: desktop Running on desktop f: gestures flutter/packages/flutter/gestures repository. labels Dec 4, 2019
@dkwingsmt
Copy link
Contributor Author

Close this to split it into multiple PRs, with potentially different approaches.

@dkwingsmt dkwingsmt closed this Dec 5, 2019
@dkwingsmt dkwingsmt reopened this Dec 5, 2019
@dkwingsmt
Copy link
Contributor Author

Never mind, the "different approach" can not be done without this.

@gspencergoog
Copy link
Contributor

Might want to update your PR description to mention the tests and any issues.

// Don't return `identityHashCode(value)` directly since the hash of this key
// needs to be different from that of its value.
@override
int get hashCode => identityHashCode(T) ^ identityHashCode(value);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
int get hashCode => identityHashCode(T) ^ identityHashCode(value);
int get hashCode => hashValues(identityHashCode(T), identityHashCode(value));

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 really want to reduce the overhead of hashCode since it will be used a lot every frame. And since it's a private class we don't need a very secure hashing. The only feature we need is that theKey.hashCode != theKey.value.hashCode.

I changed it to a XOR with a const value. What do you think?

});
}

// Render `foreground` at the top-left corner, on top of a full-screen `background`.
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for markdown here.

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 original doc was outdated. I changed it to

// Render widget `topLeft` at the top-left corner, stacking on top of the widget
// `background`.

// class.
@override
int get hashCode => _hashCodeMask ^ identityHashCode(value);
static const int _hashCodeMask = 0xc366d8df10ee8;
Copy link
Contributor

Choose a reason for hiding this comment

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

why does it need to be different? would we ever put instances of Key and instances of MouseTrackerAnnotation into the same map?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. If it's not required that hashCode must be different, I'm glad to remove the mask.

Copy link
Member

Choose a reason for hiding this comment

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

If it's not required, why do we have the mask?

bool operator ==(Object other) {
if (identical(this, other))
return true;
return other is _IdenticalAnnotationKey && identical(other.value, value);
Copy link
Contributor

Choose a reason for hiding this comment

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

import 'dart:ui' show hashValues;

import 'package:meta/meta.dart';
import './diagnostics.dart';
Copy link
Member

Choose a reason for hiding this comment

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

should this be import 'diagnostics.dart';? Also should be in its own section.

assert(unhandledEvent != null);
assert(trackedAnnotations != null);
assert(trackedAnnotationKeys != null);
// Order is important for mouse event callbacks. The `findAnnotations`
Copy link
Member

Choose a reason for hiding this comment

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

How do we keep the order now? Below the code is iterating over non-order preserving iterables, as far as I can tell.

Copy link
Contributor Author

@dkwingsmt dkwingsmt Dec 13, 2019

Choose a reason for hiding this comment

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

The lastAnnotations and nextAnnotations are still Lists, which is where order is kept. The lastKeys and nextKeys below are HashSets because they are only used to check if an annotation is hovered during the last frame / this frame.

There are tests that ensure the order is kept, such as the several tests that start at

test('should trigger callbacks between parents and children in correct order', () {

/// {@endtemplate}
/// * Attaching an annotation that has been attached will assert.
void attachAnnotation(MouseTrackerAnnotation annotation) {
void attachAnnotation(Key annotationKey) {
Copy link
Member

Choose a reason for hiding this comment

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

SHould this continue to tale an annotation and just internally grab the key from there?

Copy link
Contributor Author

@dkwingsmt dkwingsmt Dec 13, 2019

Choose a reason for hiding this comment

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

I've tried to keep it accepting MouseTrackerAnnotation for a long time during the development of this PR, but eventually removed it because it makes the implementation of RenderMouseRegion noticeably more complicated.

One of the problems is that, when the render object is detached, it needs to detach the current annotation. If detachAnnotation only accepts MouseTrackerAnnotations, then either the render object must keep its own hover annotation, or it builds a fake annotation when disposed. Keeping the hover annotation adds noticeably more bookkeeping overhead, since otherwise it could've built the annotation anew during every paint (which is how the PR is implemented currently, see here).

));
expect(exit, isNull);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener.hoverAnnotation), isFalse);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener.annotationKey), isFalse);
Copy link
Member

Choose a reason for hiding this comment

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

I believe the new breaking change policy would consider these changes as breaking.

Copy link
Contributor Author

@dkwingsmt dkwingsmt Dec 13, 2019

Choose a reason for hiding this comment

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

The isAnnotationAttached is a visibleForTest method, and is not used in g3. If this is still a concern I can use a 2-phase deprecation instead.

@dkwingsmt
Copy link
Contributor Author

@Hixie I've changed so that MouseTrackerAnnotation.key is required.

Copy link
Contributor

@gspencergoog gspencergoog left a comment

Choose a reason for hiding this comment

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

32384589-a60f0e74-c078-11e7-9bc1-e5b5287aea9d

dkwingsmt added a commit to dkwingsmt/flutter that referenced this pull request Jan 8, 2020
@dkwingsmt
Copy link
Contributor Author

Closing in favor of #48453.
Most changes in this PR was moved to #48453 except for the keys.

@dkwingsmt dkwingsmt closed this Jan 9, 2020
@dkwingsmt dkwingsmt deleted the mouse-annotation-key branch January 9, 2020 20:18
@dkwingsmt dkwingsmt mentioned this pull request May 8, 2020
13 tasks
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

a: desktop Running on desktop 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.

6 participants