Skip to content

Commit 0a178f8

Browse files
authored
CupertinoContextMenu/ContextMenuAction: Add clickable cursor for web (flutter#99519)
1 parent 6def159 commit 0a178f8

File tree

4 files changed

+118
-47
lines changed

4 files changed

+118
-47
lines changed

packages/flutter/lib/src/cupertino/context_menu.dart

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:math' as math;
66
import 'dart:ui' as ui;
7+
import 'package:flutter/foundation.dart';
78
import 'package:flutter/gestures.dart' show kMinFlingVelocity, kLongPressTimeout;
89
import 'package:flutter/scheduler.dart';
910
import 'package:flutter/services.dart';
@@ -367,17 +368,20 @@ class _CupertinoContextMenuState extends State<CupertinoContextMenu> with Ticker
367368

368369
@override
369370
Widget build(BuildContext context) {
370-
return GestureDetector(
371-
onTapCancel: _onTapCancel,
372-
onTapDown: _onTapDown,
373-
onTapUp: _onTapUp,
374-
onTap: _onTap,
375-
child: TickerMode(
376-
enabled: !_childHidden,
377-
child: Opacity(
378-
key: _childGlobalKey,
379-
opacity: _childHidden ? 0.0 : 1.0,
380-
child: widget.child,
371+
return MouseRegion(
372+
cursor: kIsWeb ? SystemMouseCursors.click : MouseCursor.defer,
373+
child: GestureDetector(
374+
onTapCancel: _onTapCancel,
375+
onTapDown: _onTapDown,
376+
onTapUp: _onTapUp,
377+
onTap: _onTap,
378+
child: TickerMode(
379+
enabled: !_childHidden,
380+
child: Opacity(
381+
key: _childGlobalKey,
382+
opacity: _childHidden ? 0.0 : 1.0,
383+
child: widget.child,
384+
),
381385
),
382386
),
383387
);

packages/flutter/lib/src/cupertino/context_menu_action.dart

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:flutter/foundation.dart';
56
import 'package:flutter/widgets.dart';
67
import 'colors.dart';
78

@@ -106,43 +107,46 @@ class _CupertinoContextMenuActionState extends State<CupertinoContextMenuAction>
106107

107108
@override
108109
Widget build(BuildContext context) {
109-
return GestureDetector(
110-
key: _globalKey,
111-
onTapDown: onTapDown,
112-
onTapUp: onTapUp,
113-
onTapCancel: onTapCancel,
114-
onTap: widget.onPressed,
115-
behavior: HitTestBehavior.opaque,
116-
child: ConstrainedBox(
117-
constraints: const BoxConstraints(
118-
minHeight: _kButtonHeight,
119-
),
120-
child: Semantics(
121-
button: true,
122-
child: Container(
123-
decoration: BoxDecoration(
124-
color: _isPressed
125-
? CupertinoDynamicColor.resolve(_kBackgroundColorPressed, context)
126-
: CupertinoDynamicColor.resolve(_kBackgroundColor, context),
127-
),
128-
padding: const EdgeInsets.symmetric(
129-
vertical: 16.0,
130-
horizontal: 10.0,
131-
),
132-
child: DefaultTextStyle(
133-
style: _textStyle,
134-
child: Row(
135-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
136-
children: <Widget>[
137-
Flexible(
138-
child: widget.child,
139-
),
140-
if (widget.trailingIcon != null)
141-
Icon(
142-
widget.trailingIcon,
143-
color: _textStyle.color,
110+
return MouseRegion(
111+
cursor: widget.onPressed != null && kIsWeb ? SystemMouseCursors.click : MouseCursor.defer,
112+
child: GestureDetector(
113+
key: _globalKey,
114+
onTapDown: onTapDown,
115+
onTapUp: onTapUp,
116+
onTapCancel: onTapCancel,
117+
onTap: widget.onPressed,
118+
behavior: HitTestBehavior.opaque,
119+
child: ConstrainedBox(
120+
constraints: const BoxConstraints(
121+
minHeight: _kButtonHeight,
122+
),
123+
child: Semantics(
124+
button: true,
125+
child: Container(
126+
decoration: BoxDecoration(
127+
color: _isPressed
128+
? CupertinoDynamicColor.resolve(_kBackgroundColorPressed, context)
129+
: CupertinoDynamicColor.resolve(_kBackgroundColor, context),
130+
),
131+
padding: const EdgeInsets.symmetric(
132+
vertical: 16.0,
133+
horizontal: 10.0,
134+
),
135+
child: DefaultTextStyle(
136+
style: _textStyle,
137+
child: Row(
138+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
139+
children: <Widget>[
140+
Flexible(
141+
child: widget.child,
144142
),
145-
],
143+
if (widget.trailingIcon != null)
144+
Icon(
145+
widget.trailingIcon,
146+
color: _textStyle.color,
147+
),
148+
],
149+
),
146150
),
147151
),
148152
),

packages/flutter/test/cupertino/context_menu_action_test.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// found in the LICENSE file.
44

55
import 'package:flutter/cupertino.dart';
6+
import 'package:flutter/foundation.dart';
7+
import 'package:flutter/gestures.dart';
8+
import 'package:flutter/rendering.dart';
69
import 'package:flutter_test/flutter_test.dart';
710

811
import '../rendering/mock_canvas.dart';
@@ -122,4 +125,29 @@ void main() {
122125
expect(_getTextStyle(tester).fontWeight, kDefaultActionWeight);
123126
});
124127

128+
testWidgets(
129+
'Hovering over Cupertino context menu action updates cursor to clickable on Web',
130+
(WidgetTester tester) async {
131+
/// Cupertino context menu action without "onPressed" callback.
132+
await tester.pumpWidget(_getApp());
133+
final Offset contextMenuAction = tester.getCenter(find.text('I am a CupertinoContextMenuAction'));
134+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
135+
await gesture.addPointer(location: contextMenuAction);
136+
await tester.pumpAndSettle();
137+
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
138+
139+
// / Cupertino context menu action with "onPressed" callback.
140+
await tester.pumpWidget(_getApp(onPressed: (){}));
141+
await gesture.moveTo(const Offset(10, 10));
142+
await tester.pumpAndSettle();
143+
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
144+
145+
await gesture.moveTo(contextMenuAction);
146+
addTearDown(gesture.removePointer);
147+
await tester.pumpAndSettle();
148+
expect(
149+
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
150+
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
151+
);
152+
});
125153
}

packages/flutter/test/cupertino/context_menu_test.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// found in the LICENSE file.
44

55
import 'package:flutter/cupertino.dart';
6+
import 'package:flutter/foundation.dart';
7+
import 'package:flutter/gestures.dart';
8+
import 'package:flutter/rendering.dart';
69
import 'package:flutter_test/flutter_test.dart';
710

811
void main() {
@@ -193,6 +196,38 @@ void main() {
193196
await tester.pumpAndSettle();
194197
expect(_findStatic(), findsOneWidget);
195198
});
199+
200+
testWidgets('Hovering over Cupertino context menu updates cursor to clickable on Web', (WidgetTester tester) async {
201+
final Widget child = _getChild();
202+
await tester.pumpWidget(CupertinoApp(
203+
home: CupertinoPageScaffold(
204+
child: Center(
205+
child: CupertinoContextMenu(
206+
actions: const <CupertinoContextMenuAction>[
207+
CupertinoContextMenuAction(
208+
child: Text('CupertinoContextMenuAction One'),
209+
),
210+
],
211+
child: child,
212+
),
213+
),
214+
),
215+
));
216+
217+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
218+
await gesture.addPointer(location: const Offset(10, 10));
219+
await tester.pumpAndSettle();
220+
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
221+
222+
final Offset contextMenu = tester.getCenter(find.byWidget(child));
223+
await gesture.moveTo(contextMenu);
224+
addTearDown(gesture.removePointer);
225+
await tester.pumpAndSettle();
226+
expect(
227+
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
228+
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
229+
);
230+
});
196231
});
197232

198233
group('CupertinoContextMenu when open', () {

0 commit comments

Comments
 (0)