Thanks to visit codestin.com
Credit goes to github.com

Skip to content

#163840 - CupertinoButton cursor doesn't change to clickable on desktop #164196

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

Merged
merged 17 commits into from
Mar 11, 2025
Merged

#163840 - CupertinoButton cursor doesn't change to clickable on desktop #164196

merged 17 commits into from
Mar 11, 2025

Conversation

srivats22
Copy link
Contributor

This PR addresses Issue number: 163840, where when hovering over a Cupertino button the mouse cursor wouldn't switch to clickable and there wasn't any option to configure it.

Adds Mouse cursor to CupertinoButton, CupertinoButton.Filled and CupertinoButton.Tinted

Fixes #163840
Part of #58192

Demo of the changes

pr_demo.mov

Code snippet showing new behavior

import 'package:flutter/cupertino.dart';

void main() => runApp(
  // const Center(child: Text('Hello, world!', key: Key('title'), textDirection: TextDirection.ltr)),
  CupertinoApp(
    theme: const CupertinoThemeData(
      brightness: Brightness.light,
    ),
    home: Center(
      child: Column(
        spacing: 5.0,
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          CupertinoButton(
            onPressed: (){},
            child: const Text('Default Cursor'),
          ),
          CupertinoButton(
            onPressed: (){},
            mouseCursor: SystemMouseCursors.grab,
            child: const Text('Custom Cursor'),
          ),
          CupertinoButton.filled(
            onPressed: (){},
            mouseCursor: SystemMouseCursors.copy,
            child: const Text('Custom Cursor 2'),
          ),
          CupertinoButton.tinted(
            onPressed: (){},
            mouseCursor: SystemMouseCursors.help,
            child: const Text('Custom Cursor 2'),
          ),
        ],
      )
    ),
  ),
);

List which issues are fixed by this PR. You must list at least one issue. An issue is not required if the PR fixes something trivial like a typo.

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

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

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: cupertino flutter/packages/flutter/cupertino repository labels Feb 26, 2025
@srivats22
Copy link
Contributor Author

Hi @victorsanni, @dkwingsmt

As mentioned in yesterday's PR: #163967 using WidgetStateProperty would be slightly more complicated as I would need to handle states separately which might be a challenge and add more scope. If this is incorrect please let me know/guide me on how to do it and I will work on it.

For now, this PR is the same as yesterday

Thank you

@dkwingsmt
Copy link
Contributor

@srivats22 I've pushed the code that uses a WidgetStateProperty default cursor to your branch. In short:

  • The default cursor keeps the current behavior under all states.
  • The build method parses mouseCursor by default and fall back to the default behavior.

@srivats22
Copy link
Contributor Author

Thank you @dkwingsmt will keep this for reference so from next time I will use this if I am working with anything related to mouse Cursor

@dkwingsmt
Copy link
Contributor

@victorsanni Can you verify my implementation and direction?

@dkwingsmt
Copy link
Contributor

@srivats22 Thank you for understanding. I think the WidgetStateProperty is needed because otherwise your newly added mouseCursor property wouldn't be able to control the diabled cursor, and if we ever wants to do it in the future it will be a breaking change.

Copy link
Contributor

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

LGTM, thank you!

@@ -430,8 +450,13 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
textStyle.fontSize != null ? textStyle.fontSize! * 1.2 : kCupertinoButtonDefaultIconSize,
);

final Set<WidgetState> states = <WidgetState>{if (!enabled) WidgetState.disabled};
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is a button, shouldn't it be possible to also track the pressed state using the _handleTapX callbacks?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes it is, but I think we don't need to implement everything in this PR. For now we can only make the custom cursor change what's available currently (which only counts disabled or not).

Copy link
Contributor

Choose a reason for hiding this comment

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

@srivats22 can we file an issue to track adding a pressed state and possible other states?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure will open a new issue and tag the issue to this PR...

if I can get more details on what needs to be addressed in that issue I can work on it as well

Copy link
Contributor

Choose a reason for hiding this comment

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

The mouse cursor should track other WidgetStates alongside the enabled state. So other states like focused, pressed, etc. Except hovered, since the mouse cursor is only useful when the button is hovered.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

understood will open one and it can be assigned to me will work on it thank you

await gesture.removePointer();

// Test disabled state mouse cursor
await tester.pumpWidget(buildButton(enabled: false, cursor: SystemMouseCursors.forbidden));
Copy link
Contributor

Choose a reason for hiding this comment

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

If the goal here is to test that the mouse cursor is resolved in different widget states, why is the cursor passed here a MouseCursor instead of _ButtonMouseCursor?

await gesture.removePointer();

// Test clicked state mouse cursor
await tester.pumpWidget(buildButton(enabled: true, cursor: SystemMouseCursors.click));
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here. But currently only enabled/disabled states are resolved so I'm not sure this entire block should exist. There is no 'clicked' state.

@@ -941,8 +941,75 @@ void main() {
await gesture.up();
expect(value, isTrue);
});

testWidgets('Mouse cursor resolves in pressed/disabled states', (WidgetTester tester) async {
Copy link
Contributor

Choose a reason for hiding this comment

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

The mouse cursor doesn't resolve the pressed state yet. Maybe change to enabled/disabled states?

Comment on lines 1004 to 1006
if (states.contains(WidgetState.hovered) || states.contains(WidgetState.pressed)) {
return SystemMouseCursors.click;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is currently a no-op/dead code since those states are not resolved.

Copy link
Contributor Author

@srivats22 srivats22 Mar 4, 2025

Choose a reason for hiding this comment

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

Would this code be better based on the comments just want to confirm before I push the changes the first test and the last test are similar and I can remove it if required:

testWidgets('Mouse cursor resolves in enabled/disabled states', (WidgetTester tester) async {
    Widget buildButton({required bool enabled, MouseCursor? cursor}) {
      return CupertinoApp(
        home: Center(
          child: CupertinoButton(
            onPressed: enabled ? () {} : null,
            mouseCursor: cursor,
            child: const Text('Tap Me'),
          ),
        ),
      );
    }

    // Test default mouse cursor
    final TestGesture gesture = await tester.createGesture(
      kind: PointerDeviceKind.mouse,
      pointer: 1,
    );
    await tester.pumpWidget(buildButton(enabled: true, cursor: const _ButtonMouseCursor()));
    await gesture.addPointer(location: tester.getCenter(find.byType(CupertinoButton)));
    await tester.pump();
    await gesture.moveTo(tester.getCenter(find.byType(CupertinoButton)));
    expect(
      RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
      SystemMouseCursors.basic,
    );
    await gesture.removePointer();

    // Test disabled state mouse cursor
    await tester.pumpWidget(buildButton(enabled: false, cursor: const _ButtonMouseCursor()));
    await gesture.addPointer(location: tester.getCenter(find.byType(CupertinoButton)));
    await tester.pump();
    await gesture.moveTo(tester.getCenter(find.byType(CupertinoButton)));
    expect(
      RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
      SystemMouseCursors.forbidden,
    );
    await gesture.removePointer();

    // Test clicked state mouse cursor
    await tester.pumpWidget(buildButton(enabled: true, cursor: const _ButtonMouseCursor()));
    await gesture.addPointer(location: tester.getCenter(find.byType(CupertinoButton)));
    await tester.pumpAndSettle();
    expect(
      RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
      SystemMouseCursors.basic,
    );
  });

engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 16, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 16, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 16, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 17, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 20, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 25, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 25, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 28, 2025
github-merge-queue bot pushed a commit that referenced this pull request May 1, 2025
<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

This PR is a continuation of
#164196. With this PR other
widget states such as pressed, and focused have been added to cupertino
button. This will resolve the following issue:
#165369

## Pre-launch Checklist

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

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

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 20, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 20, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: cupertino flutter/packages/flutter/cupertino repository framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CupertinoButton cursor doesn't change to clickable on desktop
4 participants