Conversation
b1e644f to
e64ca1b
Compare
48f59a7 to
277adb0
Compare
gspencergoog
left a comment
There was a problem hiding this comment.
Mostly just doc comments. It looks pretty good overall.
| /// defines a kind of mouse cursor, such as an arrow, a pointing hand, or an | ||
| /// I-beam. | ||
| /// | ||
| /// Internally, when the mouse pointer moves, it finds the front-most region |
There was a problem hiding this comment.
region as in MouseRegion? Maybe say [MouseRegion] then? Or is this a different region?
There was a problem hiding this comment.
In some way this is intentionally put in a vague way, because the answer depends on the cases. It can be an AnnotatedRegionLayer<MouseTrackerAnnotation>, RenderMouseRegion, or MouseRegion. There are also other render objects or widgets that do not directly use (Render)MouseRegion but directly push annotations, such as PlatformView, among a few.
The only thing we're certain of is that cursors have to be assigned to logical regions of the screen, because mouse tracking is based on positions.
| // Define the actions when a pointer should change to a prepared cursor by | ||
| // calling system functions. | ||
| Future<void> _handleActivateCursor(int device, PreparedMouseCursor cursor) async { | ||
| if (cursor is NoopMouseCursor) |
There was a problem hiding this comment.
Maybe I missunderstod what NoopMouseCursor does, but don't you have to tell the system that flutter is no longer controlling the cursor?
There was a problem hiding this comment.
"Not controlling the cursor" is only effective when the pointer moves across the "cursor boundary". When the pointer is moving within the "cursor boundary" is entirely covered by NoopMouseCursor, the cursor won't change at all.
|
The analyzer is unhappy. |
|
@Hixie @goderbauer I made some major changes:
Can you take a look? |
| MouseTrackerAnnotation result; | ||
| for (final MouseTrackerAnnotation annotation in annotations) { | ||
| if (annotation.cursor != null) { | ||
| result = annotation; | ||
| break; | ||
| } | ||
| } | ||
| return result ?? const _FallbackAnnotation(); |
There was a problem hiding this comment.
Feel free to ignore, but I find the following structure slightly clearer:
| MouseTrackerAnnotation result; | |
| for (final MouseTrackerAnnotation annotation in annotations) { | |
| if (annotation.cursor != null) { | |
| result = annotation; | |
| break; | |
| } | |
| } | |
| return result ?? const _FallbackAnnotation(); | |
| for (final MouseTrackerAnnotation annotation in annotations) { | |
| if (annotation.cursor != null) { | |
| return annotation; | |
| } | |
| } | |
| return const _FallbackAnnotation(); |
| final bool hadState = _mouseCursorStates.containsKey(device); | ||
| _mouseCursorStates.putIfAbsent(device, () { | ||
| final _MouseCursorState state = _MouseCursorState(); | ||
| state.onCursorChange = () { |
There was a problem hiding this comment.
It seems as if all _MouseCursorState always have the same onCursorChange method. Why not make it an instance method (and pass the device in in the constructor)?
| return state; | ||
| }); | ||
|
|
||
| final _MouseCursorState state = _mouseCursorStates[device]; |
There was a problem hiding this comment.
putIfAbsent above returns exactly this. Just assign it to state instead of looking it up again here?
| return; | ||
| } | ||
|
|
||
| final bool hadState = _mouseCursorStates.containsKey(device); |
There was a problem hiding this comment.
looks like this value is only used for asserts. Maybe rename it to debugHadState and wrap its assignment in an assert so it only happens when asserts are enabled?
| // prevents instantiation and extension. | ||
| MouseCursorController._(); | ||
|
|
||
| static MethodChannel get _channel => SystemChannels.mouseCursor; |
There was a problem hiding this comment.
nit: the indirection seems unnecessary, just use SystemChannels.mouseCursor below?
| @override | ||
| @protected | ||
| Future<void> performActivate(int device) async { | ||
| } |
There was a problem hiding this comment.
nit: add a comment to the body indicating why it's empty.
| String get debugDescription => ''; | ||
| } | ||
|
|
||
| /// A mouse cursor that is standard on the platform that the application is |
There was a problem hiding this comment.
nit: "standard" seems strange. Isn't it just one that ships with the platform or is available there?
There was a problem hiding this comment.
How about "native"?
/// A mouse cursor that is natively supported on the platform that the
/// application is running on.| } | ||
|
|
||
| @override | ||
| String get debugDescription => ''; |
There was a problem hiding this comment.
I am surprised that the empty string is used here.
There was a problem hiding this comment.
I added a comment to explain:
// The [debugDescription] is '' so that its toString() returns 'NoopMouseCursor()'.| /// one on the screen in hit-test order, or [SystemMouseCursors.basic] if no | ||
| /// others can be found. | ||
| /// | ||
| /// The [MouseTrackerAnnotation] is immutable and does not support varying |
There was a problem hiding this comment.
I still have some questions about the "immutable" and the change notification part. Maybe we can do a quick video sync?
| // It uses [LinkedHashSet] to keep the insertion order. | ||
| LinkedHashSet<MouseTrackerAnnotation> get annotations => _annotations; | ||
| LinkedHashSet<MouseTrackerAnnotation> _annotations = LinkedHashSet<MouseTrackerAnnotation>(); | ||
| LinkedHashSet<MouseTrackerAnnotation> _annotations = <MouseTrackerAnnotation>{} as LinkedHashSet<MouseTrackerAnnotation>; |
There was a problem hiding this comment.
Why this change? The old version (without the cast) seemed cleaner...
|
Avoiding the repaint for when cursors change adds a lot of complexibility. I am not sure if that's worth it, TBH. The repaint shouldn't be that expensive and I would image that when you need to change the cursor, you'd also be changing other aspects of the UI to "justify" the new cursor. For that, you'd have to do a repaint anyways... |
|
@goderbauer Can you take a look at the new design, which I call "session-based": Overview of design:
A demo of implementing image cursor can be found at dkwingsmt#1. |
cb9a0fe to
0201c21
Compare
|
Just adding myself to this so I can see when it's ready as we need it for our flutter project. |
|
I think the default behavior for this on web should be the cursor autochanges when it detects you are hovering a button, text, etc. Will this be the behavior for web or will it be necessary to add an extra widget on every clickable, text widget we want to show a different cursor on? Many thanks for implementing this! |
goderbauer
left a comment
There was a problem hiding this comment.
I like this design!
(haven't looked closely at the tests yet)
| /// The last event that the device observed before the update. | ||
| /// | ||
| /// If the update is triggered by a frame, it is not null, since the pointer | ||
| /// must have been added before. If the update is triggered by an event, |
There was a problem hiding this comment.
Is this only null for specific events (e.g. pointer added)? Maybe document that?
There was a problem hiding this comment.
I changed it to
/// The last event that the device observed before the update.
///
/// If the update is triggered by a frame, the [previousEvent] is never null,
/// since the pointer must have been added before.
///
/// If the update is triggered by a pointer event, the [previousEvent] is not
/// null except for cases where the event is the first event observed by the
/// pointer (which is not necessarily a [PointerAddedEvent]).| logEnters.clear(); | ||
| }); | ||
|
|
||
| testWidgets('Changing whether MouseRegion.cursor is null is effective and repaints', (WidgetTester tester) async { |
There was a problem hiding this comment.
Do you have a test to verify that the cursor of the MouseRegion underneath the one that changed it cursor to null becomes active?
There was a problem hiding this comment.
Good idea. I changed this test by adding a background MouseRegion
|
@goderbauer I think I've addressed all comments. Can you take a look again? |
|
Besides the comments, I also changed |
goderbauer
left a comment
There was a problem hiding this comment.
LGTM after comment is resolved
Description
This PR adds the basic framework for the mouse cursor system. While only system cursors are supported for now, the APIs are designed with the consideration of asynchronously loaded cursors (image cursors).
With this PR:
MouseRegion,RenderMouseRegionandMouseTrackerAnnotationhave a new propertyMouseCursor cursorthat can be used to customize mouse cursors of pointers that are hovering over the region.Inkwelland a few other widgets also have a new parameterMouseCursor mouseCursorThis PR is only the framework part of the system. Without the upcoming engine change, these changes won't take effect, but will not break existing apps either.
For more information about the design, see https://flutter.dev/go/system-mouse-cursorEdit: this design doc is out of dateRelated Issues
Tests
I added the following tests:
MouseTrackerMouseRegionMouseCursorControllerChecklist
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.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Did any tests fail when you ran them? Please read Handling breaking changes.