Skip to content

[WIP] Mouse cursor (framework)#42797

Closed
dkwingsmt wants to merge 50 commits intoflutter:masterfrom
dkwingsmt:mouse-cursor
Closed

[WIP] Mouse cursor (framework)#42797
dkwingsmt wants to merge 50 commits intoflutter:masterfrom
dkwingsmt:mouse-cursor

Conversation

@dkwingsmt
Copy link
Contributor

@dkwingsmt dkwingsmt commented Oct 15, 2019

Description

This PR is the framework side of adding mouse cursor support. The engine side (see Related Issues) is required for mouse cursor to work, but isn't required to run.

The framework side of the system consists of 3 parts:

  • MouseCursor, which are stateless, platform-independent definitions of a kind of cursor. They are extended by Flutter and app developers, and used by widgets. "A kind of cursor" here stands for an arrow, a pointing hand, etc.
  • MouseCursorPlatformDelegate, which are interfaces for operations related to mouse cursor on different platforms. They are extended by Flutter, one for each platform that supports mouse cursor, and their implementations are tightly related to their shells.
  • MouseCursorManager, which is a class for the Flutter framework (more specifically, MouseTracker) to dispatch mouse cursor changes.

Feature-wise, this PR includes:

  • System cursor, as well as a limited collection of constants SystemCursors.
  • Platform delegate on Android, GLFW, and macOS.
  • Adds mouseCursor to InkWell, InkResponse, RawMaterialButton and FloatingActionButton. (It's there for the convenience of testing, up to discussion if we want to keep them)

Future work includes:

  • Add more system cursors
  • Add custom cursors (from assets, from external links, and from fallbacks)
  • Add mouseCursor to more widgets (up to discussion)

Related Issues

Tests

I added the following tests:

Replace this with a list of the tests that you added as part of this PR. A change in behaviour with no test covering it
will likely get reverted accidentally sooner or later. PRs must include tests for all changed/updated/fixed behaviors. See Test Coverage.

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

Does your PR require Flutter developers to manually update their apps to accommodate your change?

  • Yes, this is a breaking change (Please read Handling breaking changes). Replace this with a link to the e-mail where you asked for input on this proposed change.
  • No, this is not a breaking change.

@fluttergithubbot fluttergithubbot added d: examples Sample code and demos framework flutter/packages/flutter repository. See also f: labels. c: contributor-productivity Team-specific productivity, code health, technical debt. customer: gallery Relating to flutter/gallery repository. Please transfer non-framework issues there. labels Oct 15, 2019
@dkwingsmt dkwingsmt changed the title [WIP] Mouse cursor [WIP] Mouse cursor (framework) Oct 15, 2019
@dkwingsmt dkwingsmt removed d: examples Sample code and demos c: contributor-productivity Team-specific productivity, code health, technical debt. customer: gallery Relating to flutter/gallery repository. Please transfer non-framework issues there. labels Oct 17, 2019
@dkwingsmt dkwingsmt changed the title [WIP] Mouse cursor (framework) Mouse cursor (framework) Oct 25, 2019
@fluttergithubbot fluttergithubbot added the f: material design flutter/packages/flutter/material repository. label Oct 25, 2019
@dkwingsmt dkwingsmt changed the title Mouse cursor (framework) [WIP] Mouse cursor (framework) Oct 25, 2019
@dkwingsmt dkwingsmt removed the f: material design flutter/packages/flutter/material repository. label Oct 25, 2019
/// * [MouseCursorPlatformDelegate], which uses this type to define how
/// system cursors are implemented on platforms.
enum MouseCursorSystemShape {
/// The shape that corresponds to [SystemCursors.none].
Copy link
Member

Choose a reason for hiding this comment

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

I couldn't find the class SystemCursors in this PR.

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.

Left some comments from a first pass-through to get familiar with the change.

My main high-level question is: What's the advantage of having the different platform-specific delegates on the framework side? Could the framework just send to the embedder: I want curser xyz and then the embedder translates that into the platform-specific ID for its platform?

}

/// Details for [MouseCursorPlatformDelegate.activateSystemCursor], such as the
/// target device and the system cursor shape.
Copy link
Member

Choose a reason for hiding this comment

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

From reading the implementation it sounds as if this class represents a request to change the cursor of device to systemShape? Maybe add that to the documentation?


@override
bool operator ==(dynamic other) {
if (other.runtimeType != MouseCursorPlatformActivateSystemCursorDetails)
Copy link
Member

Choose a reason for hiding this comment

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

if (other.runtimeType != runtimeType)

/// Asks the platform to change the cursor of `device` to the system cursor
/// specified by `systemShape`.
///
/// It resolves to `true` if the operation is successful, `false` if the
Copy link
Member

Choose a reason for hiding this comment

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

true and false don't need to be in `

/// Create a [MouseCursorPlatformDelegate].
const MouseCursorPlatformDelegate();

/// Asks the platform to change the cursor of `device` to the system cursor
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add that device and systemShape are provided within the details object?

/// Details for [MouseCursorPlatformDelegate.activateSystemCursor], such as the
/// target device and the system cursor shape.
@immutable
class MouseCursorPlatformActivateSystemCursorDetails {
Copy link
Member

Choose a reason for hiding this comment

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

This name is a mouthful. Can we find something a little shorter?


// The channel interface with the platform.
//
// It is separated to a class for the conventience of reference by the shell
Copy link
Member

Choose a reason for hiding this comment

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

What do you mean by this?


/// The same constant as Android's `PointerIcon.TYPE_DEFAULT`,
/// used internally to set system cursor.
static const int kPlatformConstantDefault = 1000;
Copy link
Member

Choose a reason for hiding this comment

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

Do these have to be public?

final _AndroidPlatformActions _platform;

Future<bool> _activatePlatformConstant(int platformConstant) async {
await _platform.setAsSystemCursor(platformConstant: platformConstant);
Copy link
Member

Choose a reason for hiding this comment

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

Why the indirection via _AndroidPlatformActions? Couldn't this method just implement that method call directly?

///
/// * `MouseCursor.setCursors`: Request to set the cursors of multiple pointer
/// devices. The argument is a `Map<int, int>` that maps from device to
/// cursor. See [MouseCursors] for a list of system cursors.
Copy link
Member

Choose a reason for hiding this comment

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

MouseCursors doesn't exist?

What does this map to? device id to what?

// throw errors.
if (kIsWeb)
return const MouseCursorUnsupportedPlatformDelegate();
if (Platform.isLinux) {
Copy link
Member

Choose a reason for hiding this comment

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

What's the advantage of having these different platform-specific delegates on the framework side? Could the framework just send to the embedder: I want curser xyz and then the embedder translates that into the platform-specific ID?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I'm not a fan of having this platform-specific code in the framework.

@dkwingsmt
Copy link
Contributor Author

dkwingsmt commented Nov 4, 2019

What's the advantage of having these different platform-specific delegates on the framework side? Could the framework just send to the embedder: I want curser xyz and then the embedder translates that into the platform-specific ID?

The most significant reason is that, Dart code is easier to maintain and test. We have to write the same code either in the framework or in the engine. By writing most code in Dart, we leave only a one-to-one API translator in the engine.

Copy link
Contributor Author

@dkwingsmt dkwingsmt left a comment

Choose a reason for hiding this comment

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

Answered 2 concerns. I'll work on the new design in the mean time.

// Change mouse cursor.
final MouseCursor cursor = _findDeviceCursor(nextAnnotations);
if (cursor != mouseState.cursor) {
_cursorManager.setDeviceCursor(device, cursor);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the cursor is not available, the action is considered successful, therefore older shells will not throw exceptions for not implementing a latest feature introduced in the framework. Only actual runtime errors are failures.

/// material.
final ValueChanged<bool> onHover;

/// The cursor for a mouse pointer when it enters or is hovering the region.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mouse cursors are changed based on Enter and Exit (instead of Hover), therefore it does not rely on whether the pointer is down. The short answer is nothing will be affected.

If you want to change the cursor when the mouse is down, you can assign different cursors to MouseRegion.cursor depending on the state.


import 'package:flutter/foundation.dart';

/// Internal identifiers for the system cursors supported by Flutter.
Copy link
Contributor

Choose a reason for hiding this comment

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

should it be private then?

I don't really understand the value of this enum.

abstract class MouseCursor {
/// Create a mouse cursor.
///
/// The base constructor does nothing.
Copy link
Contributor

Choose a reason for hiding this comment

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

for no-argument const constructors, please use the boilerplate that we use for all no-argument const constructors.

(this applies to other classes in this PR as well)

/// A platform-independent short description for this cursor.
///
/// It is usually one or several readable words.
String describeCursor();
Copy link
Contributor

Choose a reason for hiding this comment

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

this could be a getter

/// Create a [NoopMouseCursor].
const NoopMouseCursor();

/// Does nothing and immediately returns true.
Copy link
Contributor

Choose a reason for hiding this comment

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

we generally don't want to override the docs of virtual methods, it hides the documentation of the superclass. better to put that information in the class docs.

}

@override
String describeCursor() => 'noop';
Copy link
Contributor

Choose a reason for hiding this comment

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

"no-op"

/// * [MouseCursorPlatformDelegate], which uses this type to define how
/// system cursors are implemented on platforms.
enum MouseCursorSystemShape {
/// The shape that corresponds to [SystemCursors.none].
Copy link
Contributor

Choose a reason for hiding this comment

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

is "SystemCursors" supposed to be "SystemMouseCursors"? I couldn't find a definition of SystemCursors.


// The channel interface with the platform.
//
// It is separated to a class for the conventience of reference by the shell
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: convenience

? '<none>'
: callbacks.join(' ');
return '${describeIdentity(this)}(callbacks: $describeCallbacks)';
// TODO(dkwingsmt): Add property for cursor
Copy link
Contributor

Choose a reason for hiding this comment

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

please do this before landing

@Hixie
Copy link
Contributor

Hixie commented Nov 4, 2019

I had trouble following this PR. I kept looking for a class with detailed dartdocs explaining how everything fit together. I recommend taking whatever class you feel is the center of this API, and adding significant detail to that class's dartdocs, along the lines of the docs for RenderBox, explaining how all the parts fit together, how to create a custom cursor, how to port the API to a new platform, how to add a new system cursor, etc.

@dkwingsmt
Copy link
Contributor Author

dkwingsmt commented Nov 7, 2019

I'll close it for now until after some major changes I'm working on.

@dkwingsmt dkwingsmt closed this Nov 7, 2019
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

a: desktop Running on desktop framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants