Add mouse cursor API to widgets (phase 1) (#57628)

* Adds default cursor and/or mouseCursor property to a number of widgets.
* Adds `MaterialStateMouseCurrsor`.
diff --git a/packages/flutter/lib/src/material/bottom_navigation_bar.dart b/packages/flutter/lib/src/material/bottom_navigation_bar.dart
index ca3885f..6700a3a 100644
--- a/packages/flutter/lib/src/material/bottom_navigation_bar.dart
+++ b/packages/flutter/lib/src/material/bottom_navigation_bar.dart
@@ -6,6 +6,7 @@
 import 'dart:math' as math;
 
 import 'package:flutter/widgets.dart';
+import 'package:flutter/rendering.dart';
 import 'package:vector_math/vector_math_64.dart' show Vector3;
 
 import 'bottom_navigation_bar_theme.dart';
@@ -187,6 +188,7 @@
     this.unselectedLabelStyle,
     this.showSelectedLabels = true,
     this.showUnselectedLabels,
+    this.mouseCursor,
   }) : assert(items != null),
        assert(items.length >= 2),
        assert(
@@ -314,6 +316,12 @@
   /// Whether the labels are shown for the unselected [BottomNavigationBarItem]s.
   final bool showSelectedLabels;
 
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// tiles.
+  ///
+  /// If this property is null, [SystemMouseCursors.click] will be used.
+  final MouseCursor mouseCursor;
+
   @override
   _BottomNavigationBarState createState() => _BottomNavigationBarState();
 }
@@ -337,12 +345,14 @@
     this.showSelectedLabels,
     this.showUnselectedLabels,
     this.indexLabel,
+    @required this.mouseCursor,
     }) : assert(type != null),
          assert(item != null),
          assert(animation != null),
          assert(selected != null),
          assert(selectedLabelStyle != null),
-         assert(unselectedLabelStyle != null);
+         assert(unselectedLabelStyle != null),
+         assert(mouseCursor != null);
 
   final BottomNavigationBarType type;
   final BottomNavigationBarItem item;
@@ -359,6 +369,7 @@
   final String indexLabel;
   final bool showSelectedLabels;
   final bool showUnselectedLabels;
+  final MouseCursor mouseCursor;
 
   @override
   Widget build(BuildContext context) {
@@ -452,6 +463,7 @@
           children: <Widget>[
             InkResponse(
               onTap: onTap,
+              mouseCursor: mouseCursor,
               child: Padding(
                 padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
                 child: Column(
@@ -833,6 +845,7 @@
         );
         break;
     }
+    final MouseCursor effectiveMouseCursor = widget.mouseCursor ?? SystemMouseCursors.click;
 
     final List<Widget> tiles = <Widget>[];
     for (int i = 0; i < widget.items.length; i++) {
@@ -855,6 +868,7 @@
         showSelectedLabels: widget.showSelectedLabels ?? bottomTheme.showSelectedLabels,
         showUnselectedLabels: widget.showUnselectedLabels ?? bottomTheme.showUnselectedLabels ?? _defaultShowUnselected,
         indexLabel: localizations.tabLabel(tabIndex: i + 1, tabCount: widget.items.length),
+        mouseCursor: effectiveMouseCursor,
       ));
     }
     return tiles;
diff --git a/packages/flutter/lib/src/material/button.dart b/packages/flutter/lib/src/material/button.dart
index f8f5677..f052074 100644
--- a/packages/flutter/lib/src/material/button.dart
+++ b/packages/flutter/lib/src/material/button.dart
@@ -103,9 +103,20 @@
   /// [State.setState] is not allowed).
   final ValueChanged<bool> onHighlightChanged;
 
-  /// {@macro flutter.material.inkwell.mousecursor}
+  /// {@template flutter.material.button.mouseCursor}
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// button.
   ///
-  /// If the property is null, [SystemMouseCursor.click] is used.
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.pressed].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
+  /// {@endtemplate flutter.material.button.mouseCursor}
   final MouseCursor mouseCursor;
 
   /// Defines the default text style, with [Material.textStyle], for the
@@ -373,6 +384,10 @@
     final ShapeBorder effectiveShape =  MaterialStateProperty.resolveAs<ShapeBorder>(widget.shape, _states);
     final Offset densityAdjustment = widget.visualDensity.baseSizeAdjustment;
     final BoxConstraints effectiveConstraints = widget.visualDensity.effectiveConstraints(widget.constraints);
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
+      widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
+      _states,
+    );
     final EdgeInsetsGeometry padding = widget.padding.add(
       EdgeInsets.only(
         left: densityAdjustment.dx,
@@ -382,6 +397,7 @@
       ),
     ).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity);
 
+
     final Widget result = ConstrainedBox(
       constraints: effectiveConstraints,
       child: Material(
@@ -407,7 +423,7 @@
           onLongPress: widget.onLongPress,
           enableFeedback: widget.enableFeedback,
           customBorder: effectiveShape,
-          mouseCursor: widget.mouseCursor,
+          mouseCursor: effectiveMouseCursor,
           child: IconTheme.merge(
             data: IconThemeData(color: effectiveTextColor),
             child: Container(
diff --git a/packages/flutter/lib/src/material/checkbox.dart b/packages/flutter/lib/src/material/checkbox.dart
index 30f2fc3..286ac8f 100644
--- a/packages/flutter/lib/src/material/checkbox.dart
+++ b/packages/flutter/lib/src/material/checkbox.dart
@@ -11,6 +11,7 @@
 
 import 'constants.dart';
 import 'debug.dart';
+import 'material_state.dart';
 import 'theme.dart';
 import 'theme_data.dart';
 import 'toggleable.dart';
@@ -60,6 +61,7 @@
     @required this.value,
     this.tristate = false,
     @required this.onChanged,
+    this.mouseCursor,
     this.activeColor,
     this.checkColor,
     this.focusColor,
@@ -107,6 +109,23 @@
   /// ```
   final ValueChanged<bool> onChanged;
 
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  ///
+  /// When [value] is null and [tristate] is true, [MaterialState.selected] is
+  /// included as a state.
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
+  final MouseCursor mouseCursor;
+
   /// The color to use when this checkbox is checked.
   ///
   /// Defaults to [ThemeData.toggleableActiveColor].
@@ -226,6 +245,16 @@
     }
     size += (widget.visualDensity ?? themeData.visualDensity).baseSizeAdjustment;
     final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
+      widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
+      <MaterialState>{
+        if (!enabled) MaterialState.disabled,
+        if (_hovering) MaterialState.hovered,
+        if (_focused) MaterialState.focused,
+        if (widget.tristate || widget.value) MaterialState.selected,
+      },
+    );
+
     return FocusableActionDetector(
       actions: _actionMap,
       focusNode: widget.focusNode,
@@ -233,6 +262,7 @@
       enabled: enabled,
       onShowFocusHighlight: _handleFocusHighlightChanged,
       onShowHoverHighlight: _handleHoverChanged,
+      mouseCursor: effectiveMouseCursor,
       child: Builder(
         builder: (BuildContext context) {
           return _CheckboxRenderObjectWidget(
@@ -309,8 +339,10 @@
   @override
   void updateRenderObject(BuildContext context, _RenderCheckbox renderObject) {
     renderObject
-      ..value = value
+      // The `tristate` must be changed before `value` due to the assertion at
+      // the beginning of `set value`.
       ..tristate = tristate
+      ..value = value
       ..activeColor = activeColor
       ..checkColor = checkColor
       ..inactiveColor = inactiveColor
diff --git a/packages/flutter/lib/src/material/flat_button.dart b/packages/flutter/lib/src/material/flat_button.dart
index 2c580d4..da56989 100644
--- a/packages/flutter/lib/src/material/flat_button.dart
+++ b/packages/flutter/lib/src/material/flat_button.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter/foundation.dart';
+import 'package:flutter/rendering.dart';
 import 'package:flutter/widgets.dart';
 
 import 'button.dart';
@@ -104,6 +105,7 @@
     @required VoidCallback onPressed,
     VoidCallback onLongPress,
     ValueChanged<bool> onHighlightChanged,
+    MouseCursor mouseCursor,
     ButtonTextTheme textTheme,
     Color textColor,
     Color disabledTextColor,
@@ -129,6 +131,7 @@
          onPressed: onPressed,
          onLongPress: onLongPress,
          onHighlightChanged: onHighlightChanged,
+         mouseCursor: mouseCursor,
          textTheme: textTheme,
          textColor: textColor,
          disabledTextColor: disabledTextColor,
@@ -161,6 +164,7 @@
     @required VoidCallback onPressed,
     VoidCallback onLongPress,
     ValueChanged<bool> onHighlightChanged,
+    MouseCursor mouseCursor,
     ButtonTextTheme textTheme,
     Color textColor,
     Color disabledTextColor,
@@ -189,6 +193,7 @@
       onPressed: onPressed,
       onLongPress: onLongPress,
       onHighlightChanged: onHighlightChanged,
+      mouseCursor: mouseCursor,
       fillColor: buttonTheme.getFillColor(this),
       textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)),
       focusColor: buttonTheme.getFocusColor(this),
@@ -224,6 +229,7 @@
     @required VoidCallback onPressed,
     VoidCallback onLongPress,
     ValueChanged<bool> onHighlightChanged,
+    MouseCursor mouseCursor,
     ButtonTextTheme textTheme,
     Color textColor,
     Color disabledTextColor,
@@ -251,6 +257,7 @@
          onPressed: onPressed,
          onLongPress: onLongPress,
          onHighlightChanged: onHighlightChanged,
+         mouseCursor: mouseCursor,
          textTheme: textTheme,
          textColor: textColor,
          disabledTextColor: disabledTextColor,
diff --git a/packages/flutter/lib/src/material/floating_action_button.dart b/packages/flutter/lib/src/material/floating_action_button.dart
index fe6e945..cc3211c 100644
--- a/packages/flutter/lib/src/material/floating_action_button.dart
+++ b/packages/flutter/lib/src/material/floating_action_button.dart
@@ -142,9 +142,9 @@
     this.highlightElevation,
     this.disabledElevation,
     @required this.onPressed,
+    this.mouseCursor,
     this.mini = false,
     this.shape,
-    this.mouseCursor,
     this.clipBehavior = Clip.none,
     this.focusNode,
     this.autofocus = false,
@@ -183,8 +183,8 @@
     this.highlightElevation,
     this.disabledElevation,
     @required this.onPressed,
+    this.mouseCursor = SystemMouseCursors.click,
     this.shape,
-    this.mouseCursor,
     this.isExtended = true,
     this.materialTapTargetSize,
     this.clipBehavior = Clip.none,
@@ -290,6 +290,9 @@
   /// If this is set to null, the button will be disabled.
   final VoidCallback onPressed;
 
+  /// {@macro flutter.material.button.mouseCursor}
+  final MouseCursor mouseCursor;
+
   /// The z-coordinate at which to place this button relative to its parent.
   ///
   /// This controls the size of the shadow below the floating action button.
@@ -378,11 +381,6 @@
   /// shape as well.
   final ShapeBorder shape;
 
-  /// {@macro flutter.material.inkwell.mousecursor}
-  ///
-  /// If the property is null, [SystemMouseCursor.click] is used.
-  final MouseCursor mouseCursor;
-
   /// {@macro flutter.widgets.Clip}
   ///
   /// Defaults to [Clip.none], and must not be null.
diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart
index 8bec7b3..c30869e 100644
--- a/packages/flutter/lib/src/material/icon_button.dart
+++ b/packages/flutter/lib/src/material/icon_button.dart
@@ -5,6 +5,7 @@
 import 'dart:math' as math;
 
 import 'package:flutter/foundation.dart';
+import 'package:flutter/rendering.dart';
 import 'package:flutter/widgets.dart';
 
 import 'constants.dart';
@@ -151,6 +152,7 @@
     this.splashColor,
     this.disabledColor,
     @required this.onPressed,
+    this.mouseCursor = SystemMouseCursors.click,
     this.focusNode,
     this.autofocus = false,
     this.tooltip,
@@ -274,6 +276,11 @@
   /// If this is set to null, the button will be disabled.
   final VoidCallback onPressed;
 
+  /// {@macro flutter.material.inkwell.mousecursor}
+  ///
+  /// Defaults to [SystemMouseCursors.click].
+  final MouseCursor mouseCursor;
+
   /// {@macro flutter.widgets.Focus.focusNode}
   final FocusNode focusNode;
 
@@ -370,6 +377,7 @@
         autofocus: autofocus,
         canRequestFocus: onPressed != null,
         onTap: onPressed,
+        mouseCursor: mouseCursor,
         enableFeedback: enableFeedback,
         child: result,
         focusColor: focusColor ?? theme.focusColor,
diff --git a/packages/flutter/lib/src/material/ink_well.dart b/packages/flutter/lib/src/material/ink_well.dart
index 3be189b..9a4f1c0 100644
--- a/packages/flutter/lib/src/material/ink_well.dart
+++ b/packages/flutter/lib/src/material/ink_well.dart
@@ -284,8 +284,8 @@
   ///
   /// Must have an ancestor [Material] widget in which to cause ink reactions.
   ///
-  /// The [containedInkWell], [highlightShape], [enableFeedback], and
-  /// [excludeFromSemantics] arguments must not be null.
+  /// The [mouseCursor], [containedInkWell], [highlightShape], [enableFeedback],
+  /// and [excludeFromSemantics] arguments must not be null.
   const InkResponse({
     Key key,
     this.child,
@@ -296,7 +296,7 @@
     this.onLongPress,
     this.onHighlightChanged,
     this.onHover,
-    this.mouseCursor,
+    this.mouseCursor = MouseCursor.defer,
     this.containedInkWell = false,
     this.highlightShape = BoxShape.circle,
     this.radius,
@@ -313,7 +313,8 @@
     this.canRequestFocus = true,
     this.onFocusChange,
     this.autofocus = false,
-  }) : assert(containedInkWell != null),
+  }) : assert(mouseCursor != null),
+       assert(containedInkWell != null),
        assert(highlightShape != null),
        assert(enableFeedback != null),
        assert(excludeFromSemantics != null),
@@ -363,12 +364,11 @@
   /// material.
   final ValueChanged<bool> onHover;
 
-  /// {@template flutter.material.inkwell.mousecursor}
   /// The cursor for a mouse pointer when it enters or is hovering over the
-  /// region.
-  /// {@endtemplate}
+  /// widget.
   ///
-  /// If the property is null, [SystemMouseCursor.click] is used.
+  /// The [cursor] defaults to [MouseCursor.defer], deferring the choice of
+  /// cursor to the next region behing it in hit-test order.
   final MouseCursor mouseCursor;
 
   /// Whether this ink response should be clipped its bounds.
@@ -544,7 +544,7 @@
   @override
   Widget build(BuildContext context) {
     final _ParentInkResponseState parentState = _ParentInkResponseProvider.of(context);
-    return _InnerInkResponse(
+    return _InkResponseStateWidget(
       child: child,
       onTap: onTap,
       onTapDown: onTapDown,
@@ -553,7 +553,7 @@
       onLongPress: onLongPress,
       onHighlightChanged: onHighlightChanged,
       onHover: onHover,
-      mouseCursor: mouseCursor ?? SystemMouseCursors.click,
+      mouseCursor: mouseCursor,
       containedInkWell: containedInkWell,
       highlightShape: highlightShape,
       radius: radius,
@@ -591,8 +591,8 @@
   }
 }
 
-class _InnerInkResponse extends StatefulWidget {
-  const _InnerInkResponse({
+class _InkResponseStateWidget extends StatefulWidget {
+  const _InkResponseStateWidget({
     this.child,
     this.onTap,
     this.onTapDown,
@@ -601,7 +601,7 @@
     this.onLongPress,
     this.onHighlightChanged,
     this.onHover,
-    this.mouseCursor,
+    this.mouseCursor = MouseCursor.defer,
     this.containedInkWell = false,
     this.highlightShape = BoxShape.circle,
     this.radius,
@@ -626,7 +626,8 @@
        assert(enableFeedback != null),
        assert(excludeFromSemantics != null),
        assert(autofocus != null),
-       assert(canRequestFocus != null);
+       assert(canRequestFocus != null),
+       assert(mouseCursor != null);
 
   final Widget child;
   final GestureTapCallback onTap;
@@ -671,6 +672,7 @@
       if (onTapCancel != null) 'tap cancel',
     ];
     properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
+    properties.add(DiagnosticsProperty<MouseCursor>('mouseCursor', mouseCursor, defaultValue: MouseCursor.defer));
     properties.add(DiagnosticsProperty<bool>('containedInkWell', containedInkWell, level: DiagnosticLevel.fine));
     properties.add(DiagnosticsProperty<BoxShape>(
       'highlightShape',
@@ -689,8 +691,8 @@
   focus,
 }
 
-class _InkResponseState extends State<_InnerInkResponse>
-    with AutomaticKeepAliveClientMixin<_InnerInkResponse>
+class _InkResponseState extends State<_InkResponseStateWidget>
+    with AutomaticKeepAliveClientMixin<_InkResponseStateWidget>
     implements _ParentInkResponseState {
   Set<InteractiveInkFeature> _splashes;
   InteractiveInkFeature _currentSplash;
@@ -732,7 +734,7 @@
   }
 
   @override
-  void didUpdateWidget(_InnerInkResponse oldWidget) {
+  void didUpdateWidget(_InkResponseStateWidget oldWidget) {
     super.didUpdateWidget(oldWidget);
     if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) {
       _handleHoverChange(_hovering);
@@ -988,7 +990,7 @@
     super.deactivate();
   }
 
-  bool _isWidgetEnabled(_InnerInkResponse widget) {
+  bool _isWidgetEnabled(_InkResponseStateWidget widget) {
     return widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null;
   }
 
@@ -1146,8 +1148,8 @@
   ///
   /// Must have an ancestor [Material] widget in which to cause ink reactions.
   ///
-  /// The [enableFeedback] and [excludeFromSemantics] arguments must not be
-  /// null.
+  /// The [mouseCursor], [enableFeedback], and [excludeFromSemantics] arguments
+  /// must not be null.
   const InkWell({
     Key key,
     Widget child,
@@ -1158,7 +1160,7 @@
     GestureTapCancelCallback onTapCancel,
     ValueChanged<bool> onHighlightChanged,
     ValueChanged<bool> onHover,
-    MouseCursor mouseCursor,
+    MouseCursor mouseCursor = MouseCursor.defer,
     Color focusColor,
     Color hoverColor,
     Color highlightColor,
diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart
index a423a16..4f4f1b9 100644
--- a/packages/flutter/lib/src/material/list_tile.dart
+++ b/packages/flutter/lib/src/material/list_tile.dart
@@ -13,6 +13,7 @@
 import 'debug.dart';
 import 'divider.dart';
 import 'ink_well.dart';
+import 'material_state.dart';
 import 'theme.dart';
 import 'theme_data.dart';
 
@@ -640,6 +641,7 @@
     this.enabled = true,
     this.onTap,
     this.onLongPress,
+    this.mouseCursor,
     this.selected = false,
     this.focusColor,
     this.hoverColor,
@@ -736,6 +738,18 @@
   /// Inoperative if [enabled] is false.
   final GestureLongPressCallback onLongPress;
 
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
+  final MouseCursor mouseCursor;
+
   /// If this tile is also [enabled] then icons and text are rendered with the same color.
   ///
   /// By default the selected color is the theme's primary color. The selected color
@@ -909,9 +923,18 @@
       ?? tileTheme?.contentPadding?.resolve(textDirection)
       ?? _defaultContentPadding;
 
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
+      mouseCursor ?? MaterialStateMouseCursor.clickable,
+      <MaterialState>{
+        if (!enabled) MaterialState.disabled,
+        if (selected) MaterialState.selected,
+      },
+    );
+
     return InkWell(
       onTap: enabled ? onTap : null,
       onLongPress: enabled ? onLongPress : null,
+      mouseCursor: effectiveMouseCursor,
       canRequestFocus: enabled,
       focusNode: focusNode,
       focusColor: focusColor,
diff --git a/packages/flutter/lib/src/material/material_button.dart b/packages/flutter/lib/src/material/material_button.dart
index 8bcaf48..feb44c9 100644
--- a/packages/flutter/lib/src/material/material_button.dart
+++ b/packages/flutter/lib/src/material/material_button.dart
@@ -53,6 +53,7 @@
     @required this.onPressed,
     this.onLongPress,
     this.onHighlightChanged,
+    this.mouseCursor,
     this.textTheme,
     this.textColor,
     this.disabledTextColor,
@@ -115,6 +116,9 @@
   /// [State.setState] is not allowed).
   final ValueChanged<bool> onHighlightChanged;
 
+  /// {@macro flutter.material.button.mouseCursor}
+  final MouseCursor mouseCursor;
+
   /// Defines the button's base colors, and the defaults for the button's minimum
   /// size, internal padding, and shape.
   ///
@@ -387,6 +391,7 @@
       onLongPress: onLongPress,
       enableFeedback: enableFeedback,
       onHighlightChanged: onHighlightChanged,
+      mouseCursor: mouseCursor,
       fillColor: buttonTheme.getFillColor(this),
       textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)),
       focusColor: focusColor ?? buttonTheme.getFocusColor(this) ?? theme.focusColor,
diff --git a/packages/flutter/lib/src/material/material_state.dart b/packages/flutter/lib/src/material/material_state.dart
index 2d4936e..5d64c89 100644
--- a/packages/flutter/lib/src/material/material_state.dart
+++ b/packages/flutter/lib/src/material/material_state.dart
@@ -4,6 +4,9 @@
 
 import 'dart:ui' show Color;
 
+import 'package:flutter/foundation.dart';
+import 'package:flutter/rendering.dart';
+
 /// Interactive states that some of the Material widgets can take on when
 /// receiving input from the user.
 ///
@@ -178,6 +181,98 @@
   Color resolve(Set<MaterialState> states) => _resolve(states);
 }
 
+
+/// Defines a [MouseCursor] whose value depends on a set of [MaterialState]s which
+/// represent the interactive state of a component.
+///
+/// This kind of [MouseCursor] is useful when the set of interactive actions a
+/// widget supports varies with its state. For example, a mouse pointer hovering
+/// over a disabled [FlatButton] should not display [SystemMouseCursors.click],
+/// since the button is not clickable. To solve this, you can use
+/// [MaterialStateMouseCursor] to assign a different cursor (such as
+/// [SystemMouseCursors.basic]) when the [FlatButton] is disabled.
+///
+/// To use a [MaterialStateMouseCursor], you should create a subclass of
+/// [MaterialStateMouseCursor] and implement the abstract `resolve` method.
+///
+/// {@tool snippet}
+///
+/// In this next example, we see how you can create a `MaterialStateMouseCursor` by
+/// extending the abstract class and overriding the `resolve` method.
+///
+/// ```dart
+/// class ButtonCursor extends MaterialStateMouseCursor {
+///   const ButtonCursor();
+///
+///   @override
+///   MouseCursor resolve(Set<MaterialState> states) {
+///     if (states.contains(MaterialState.disabled)) {
+///       return SystemMouseCursors.forbidden;
+///     }
+///     return SystemMouseCursors.click;
+///   }
+///
+///   @override
+///   String get debugDescription => 'ButtonCursor()';
+/// }
+///
+/// class MyFlatButton extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return FlatButton(
+///       child: Text('FlatButton'),
+///       onPressed: () {},
+///       mouseCursor: const ButtonCursor(),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// This should only be used as parameters when they are documented to take
+/// [MaterialStateMouseCursor], otherwise only the default state will be used.
+abstract class MaterialStateMouseCursor extends MouseCursor implements MaterialStateProperty<MouseCursor> {
+  /// Creates a [MaterialStateMouseCursor].
+  const MaterialStateMouseCursor();
+
+  @protected
+  @override
+  MouseCursorSession createSession(int device) {
+    return resolve(<MaterialState>{}).createSession(device);
+  }
+
+  /// Returns a [MouseCursor] that's to be used when a Material component is in
+  /// the specified state.
+  ///
+  /// This method should never return null.
+  @override
+  MouseCursor resolve(Set<MaterialState> states);
+
+  /// A mouse cursor for clickable material widgets, which resolves differently
+  /// when the widget is disabled.
+  ///
+  /// By default this cursor resolves to [SystemMouseCursors.click]. If the widget is
+  /// disabled, the cursor resolves to [SystemMouseCursors.basic].
+  ///
+  /// This cursor is the default for many Material widgets.
+  static const MaterialStateMouseCursor clickable = _ClickableMouseCursor();
+}
+
+class _ClickableMouseCursor extends MaterialStateMouseCursor {
+  const _ClickableMouseCursor();
+
+  @override
+  MouseCursor resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.disabled)) {
+      return SystemMouseCursors.basic;
+    }
+    return SystemMouseCursors.click;
+  }
+
+  @override
+  String get debugDescription => 'MaterialStateMouseCursor(clickable)';
+}
+
 /// Interface for classes that can return a value of type `T` based on a set of
 /// [MaterialState]s.
 ///
diff --git a/packages/flutter/lib/src/material/outline_button.dart b/packages/flutter/lib/src/material/outline_button.dart
index 2d7fcf0..95de4af 100644
--- a/packages/flutter/lib/src/material/outline_button.dart
+++ b/packages/flutter/lib/src/material/outline_button.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter/foundation.dart';
+import 'package:flutter/rendering.dart';
 import 'package:flutter/widgets.dart';
 
 import 'button_theme.dart';
@@ -63,6 +64,7 @@
     Key key,
     @required VoidCallback onPressed,
     VoidCallback onLongPress,
+    MouseCursor mouseCursor,
     ButtonTextTheme textTheme,
     Color textColor,
     Color disabledTextColor,
@@ -89,6 +91,7 @@
          key: key,
          onPressed: onPressed,
          onLongPress: onLongPress,
+         mouseCursor: mouseCursor,
          textTheme: textTheme,
          textColor: textColor,
          disabledTextColor: disabledTextColor,
@@ -119,6 +122,7 @@
     Key key,
     @required VoidCallback onPressed,
     VoidCallback onLongPress,
+    MouseCursor mouseCursor,
     ButtonTextTheme textTheme,
     Color textColor,
     Color disabledTextColor,
@@ -178,6 +182,7 @@
       autofocus: autofocus,
       onPressed: onPressed,
       onLongPress: onLongPress,
+      mouseCursor: mouseCursor,
       brightness: buttonTheme.getBrightness(this),
       textTheme: textTheme,
       textColor: buttonTheme.getTextColor(this),
@@ -218,6 +223,7 @@
     Key key,
     @required VoidCallback onPressed,
     VoidCallback onLongPress,
+    MouseCursor mouseCursor,
     ButtonTextTheme textTheme,
     Color textColor,
     Color disabledTextColor,
@@ -247,6 +253,7 @@
          key: key,
          onPressed: onPressed,
          onLongPress: onLongPress,
+         mouseCursor: mouseCursor,
          textTheme: textTheme,
          textColor: textColor,
          disabledTextColor: disabledTextColor,
@@ -281,6 +288,7 @@
     Key key,
     @required this.onPressed,
     this.onLongPress,
+    this.mouseCursor,
     this.brightness,
     this.textTheme,
     this.textColor,
@@ -309,6 +317,7 @@
 
   final VoidCallback onPressed;
   final VoidCallback onLongPress;
+  final MouseCursor mouseCursor;
   final Brightness brightness;
   final ButtonTextTheme textTheme;
   final Color textColor;
@@ -462,6 +471,7 @@
           disabledColor: Colors.transparent,
           onPressed: widget.onPressed,
           onLongPress: widget.onLongPress,
+          mouseCursor: widget.mouseCursor,
           elevation: 0.0,
           disabledElevation: 0.0,
           focusElevation: 0.0,
diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart
index 955e184..56ba76c 100644
--- a/packages/flutter/lib/src/material/popup_menu.dart
+++ b/packages/flutter/lib/src/material/popup_menu.dart
@@ -17,6 +17,7 @@
 import 'list_tile.dart';
 import 'material.dart';
 import 'material_localizations.dart';
+import 'material_state.dart';
 import 'popup_menu_theme.dart';
 import 'theme.dart';
 import 'tooltip.dart';
@@ -216,6 +217,7 @@
     this.enabled = true,
     this.height = kMinInteractiveDimension,
     this.textStyle,
+    this.mouseCursor,
     @required this.child,
   }) : assert(enabled != null),
        assert(height != null),
@@ -242,6 +244,17 @@
   /// If [PopupMenuThemeData.textStyle] is also null, then [ThemeData.textTheme.subtitle1] is used.
   final TextStyle textStyle;
 
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]:
+  ///
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
+  final MouseCursor mouseCursor;
+
   /// The widget below this widget in the tree.
   ///
   /// Typically a single-line [ListTile] (for menus with icons) or a [Text]. An
@@ -320,10 +333,17 @@
         child: item,
       );
     }
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
+      widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
+      <MaterialState>{
+        if (!widget.enabled) MaterialState.disabled,
+      },
+    );
 
     return InkWell(
       onTap: widget.enabled ? handleTap : null,
       canRequestFocus: widget.enabled,
+      mouseCursor: effectiveMouseCursor,
       child: item,
     );
   }
diff --git a/packages/flutter/lib/src/material/radio.dart b/packages/flutter/lib/src/material/radio.dart
index dbfe290..42ec911 100644
--- a/packages/flutter/lib/src/material/radio.dart
+++ b/packages/flutter/lib/src/material/radio.dart
@@ -9,6 +9,7 @@
 
 import 'constants.dart';
 import 'debug.dart';
+import 'material_state.dart';
 import 'theme.dart';
 import 'theme_data.dart';
 import 'toggleable.dart';
@@ -108,6 +109,7 @@
     @required this.value,
     @required this.groupValue,
     @required this.onChanged,
+    this.mouseCursor,
     this.toggleable = false,
     this.activeColor,
     this.focusColor,
@@ -157,6 +159,20 @@
   /// ```
   final ValueChanged<T> onChanged;
 
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
+  final MouseCursor mouseCursor;
+
   /// Set to true if this radio button is allowed to be returned to an
   /// indeterminate state by selecting it again when selected.
   ///
@@ -325,17 +341,29 @@
     }
     size += (widget.visualDensity ?? themeData.visualDensity).baseSizeAdjustment;
     final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
+    final bool selected = widget.value == widget.groupValue;
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
+      widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
+      <MaterialState>{
+        if (!enabled) MaterialState.disabled,
+        if (_hovering) MaterialState.hovered,
+        if (_focused) MaterialState.focused,
+        if (selected) MaterialState.selected,
+      },
+    );
+
     return FocusableActionDetector(
       actions: _actionMap,
       focusNode: widget.focusNode,
       autofocus: widget.autofocus,
+      mouseCursor: effectiveMouseCursor,
       enabled: enabled,
       onShowFocusHighlight: _handleHighlightChanged,
       onShowHoverHighlight: _handleHoverChanged,
       child: Builder(
         builder: (BuildContext context) {
           return _RadioRenderObjectWidget(
-            selected: widget.value == widget.groupValue,
+            selected: selected,
             activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
             inactiveColor: _getInactiveColor(themeData),
             focusColor: widget.focusColor ?? themeData.focusColor,
diff --git a/packages/flutter/lib/src/material/raised_button.dart b/packages/flutter/lib/src/material/raised_button.dart
index eddd7a3..6373b72 100644
--- a/packages/flutter/lib/src/material/raised_button.dart
+++ b/packages/flutter/lib/src/material/raised_button.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter/foundation.dart';
+import 'package:flutter/rendering.dart';
 import 'package:flutter/widgets.dart';
 
 import 'button.dart';
@@ -110,6 +111,7 @@
     @required VoidCallback onPressed,
     VoidCallback onLongPress,
     ValueChanged<bool> onHighlightChanged,
+    MouseCursor mouseCursor,
     ButtonTextTheme textTheme,
     Color textColor,
     Color disabledTextColor,
@@ -146,6 +148,7 @@
          onPressed: onPressed,
          onLongPress: onLongPress,
          onHighlightChanged: onHighlightChanged,
+         mouseCursor: mouseCursor,
          textTheme: textTheme,
          textColor: textColor,
          disabledTextColor: disabledTextColor,
@@ -185,6 +188,7 @@
     @required VoidCallback onPressed,
     VoidCallback onLongPress,
     ValueChanged<bool> onHighlightChanged,
+    MouseCursor mouseCursor,
     ButtonTextTheme textTheme,
     Color textColor,
     Color disabledTextColor,
@@ -217,6 +221,7 @@
       onPressed: onPressed,
       onLongPress: onLongPress,
       onHighlightChanged: onHighlightChanged,
+      mouseCursor: mouseCursor,
       clipBehavior: clipBehavior,
       fillColor: buttonTheme.getFillColor(this),
       textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)),
@@ -262,6 +267,7 @@
     @required VoidCallback onPressed,
     VoidCallback onLongPress,
     ValueChanged<bool> onHighlightChanged,
+    MouseCursor mouseCursor,
     ButtonTextTheme textTheme,
     Color textColor,
     Color disabledTextColor,
@@ -296,6 +302,7 @@
          onPressed: onPressed,
          onLongPress: onLongPress,
          onHighlightChanged: onHighlightChanged,
+         mouseCursor: mouseCursor,
          textTheme: textTheme,
          textColor: textColor,
          disabledTextColor: disabledTextColor,
diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart
index 4498934..96ca44b 100644
--- a/packages/flutter/lib/src/material/slider.dart
+++ b/packages/flutter/lib/src/material/slider.dart
@@ -17,6 +17,7 @@
 import 'constants.dart';
 import 'debug.dart';
 import 'material.dart';
+import 'material_state.dart';
 import 'slider_theme.dart';
 import 'theme.dart';
 
@@ -159,6 +160,7 @@
     this.label,
     this.activeColor,
     this.inactiveColor,
+    this.mouseCursor,
     this.semanticFormatterCallback,
     this.focusNode,
     this.autofocus = false,
@@ -188,6 +190,7 @@
     this.max = 1.0,
     this.divisions,
     this.label,
+    this.mouseCursor,
     this.activeColor,
     this.inactiveColor,
     this.semanticFormatterCallback,
@@ -381,6 +384,19 @@
   /// Ignored if this slider is created with [Slider.adaptive].
   final Color inactiveColor;
 
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
+  final MouseCursor mouseCursor;
+
   /// The callback used to create a semantic value from a slider value.
   ///
   /// Defaults to formatting values as a percentage.
@@ -537,7 +553,7 @@
     widget.onChangeEnd(_lerp(value));
   }
 
-  void _actionHandler (_AdjustSliderIntent intent) {
+  void _actionHandler(_AdjustSliderIntent intent) {
     final _RenderSlider renderSlider = _renderObjectKey.currentContext.findRenderObject() as _RenderSlider;
     final TextDirection textDirection = Directionality.of(_renderObjectKey.currentContext);
     switch (intent.type) {
@@ -682,6 +698,14 @@
         color: theme.colorScheme.onPrimary,
       ),
     );
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
+      widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
+      <MaterialState>{
+        if (!_enabled) MaterialState.disabled,
+        if (_hovering) MaterialState.hovered,
+        if (_focused) MaterialState.focused,
+      },
+    );
 
     // This size is used as the max bounds for the painting of the value
     // indicators It must be kept in sync with the function with the same name
@@ -696,6 +720,7 @@
       enabled: _enabled,
       onShowFocusHighlight: _handleFocusHighlightChanged,
       onShowHoverHighlight: _handleHoverChanged,
+      mouseCursor: effectiveMouseCursor,
       child: CompositedTransformTarget(
         link: _layerLink,
         child: _SliderRenderObjectWidget(
diff --git a/packages/flutter/lib/src/material/switch.dart b/packages/flutter/lib/src/material/switch.dart
index 86698c6..1bf29a8 100644
--- a/packages/flutter/lib/src/material/switch.dart
+++ b/packages/flutter/lib/src/material/switch.dart
@@ -11,6 +11,7 @@
 import 'colors.dart';
 import 'constants.dart';
 import 'debug.dart';
+import 'material_state.dart';
 import 'shadows.dart';
 import 'theme.dart';
 import 'theme_data.dart';
@@ -76,6 +77,7 @@
     this.onInactiveThumbImageError,
     this.materialTapTargetSize,
     this.dragStartBehavior = DragStartBehavior.start,
+    this.mouseCursor,
     this.focusColor,
     this.hoverColor,
     this.focusNode,
@@ -109,6 +111,7 @@
     this.onInactiveThumbImageError,
     this.materialTapTargetSize,
     this.dragStartBehavior = DragStartBehavior.start,
+    this.mouseCursor,
     this.focusColor,
     this.hoverColor,
     this.focusNode,
@@ -206,6 +209,20 @@
   /// {@macro flutter.cupertino.switch.dragStartBehavior}
   final DragStartBehavior dragStartBehavior;
 
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
+  final MouseCursor mouseCursor;
+
   /// The color for the button's [Material] when it has the input focus.
   final Color focusColor;
 
@@ -303,6 +320,15 @@
       inactiveThumbColor = widget.inactiveThumbColor ?? (isDark ? Colors.grey.shade800 : Colors.grey.shade400);
       inactiveTrackColor = widget.inactiveTrackColor ?? (isDark ? Colors.white10 : Colors.black12);
     }
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
+      widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
+      <MaterialState>{
+        if (!enabled) MaterialState.disabled,
+        if (_hovering) MaterialState.hovered,
+        if (_focused) MaterialState.focused,
+        if (widget.value) MaterialState.selected,
+      },
+    );
 
     return FocusableActionDetector(
       actions: _actionMap,
@@ -311,6 +337,7 @@
       enabled: enabled,
       onShowFocusHighlight: _handleFocusHighlightChanged,
       onShowHoverHighlight: _handleHoverChanged,
+      mouseCursor: effectiveMouseCursor,
       child: Builder(
         builder: (BuildContext context) {
           return _SwitchRenderObjectWidget(
diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart
index d9cf9c6..248e82e 100644
--- a/packages/flutter/lib/src/material/tabs.dart
+++ b/packages/flutter/lib/src/material/tabs.dart
@@ -611,6 +611,7 @@
     this.unselectedLabelColor,
     this.unselectedLabelStyle,
     this.dragStartBehavior = DragStartBehavior.start,
+    this.mouseCursor,
     this.onTap,
   }) : assert(tabs != null),
        assert(isScrollable != null),
@@ -734,6 +735,12 @@
   /// {@macro flutter.widgets.scrollable.dragStartBehavior}
   final DragStartBehavior dragStartBehavior;
 
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// individual tab widgets.
+  ///
+  /// If this property is null, [SystemMouseCursors.click] will be used.
+  final MouseCursor mouseCursor;
+
   /// An optional callback that's called when the [TabBar] is tapped.
   ///
   /// The callback is applied to the index of the tab where the tap occurred.
@@ -1067,6 +1074,7 @@
     final int tabCount = widget.tabs.length;
     for (int index = 0; index < tabCount; index += 1) {
       wrappedTabs[index] = InkWell(
+        mouseCursor: widget.mouseCursor ?? SystemMouseCursors.click,
         onTap: () { _handleTap(index); },
         child: Padding(
           padding: EdgeInsets.only(bottom: widget.indicatorWeight),
diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart
index f50457c..80e756b 100644
--- a/packages/flutter/lib/src/material/text_field.dart
+++ b/packages/flutter/lib/src/material/text_field.dart
@@ -1087,6 +1087,7 @@
         onSelectionHandleTapped: _handleSelectionHandleTapped,
         inputFormatters: formatters,
         rendererIgnoresPointer: true,
+        mouseCursor: MouseCursor.defer, // TextField will handle the cursor
         cursorWidth: widget.cursorWidth,
         cursorRadius: cursorRadius,
         cursorColor: cursorColor,
@@ -1129,6 +1130,7 @@
     return IgnorePointer(
       ignoring: !_isEnabled,
       child: MouseRegion(
+        cursor: SystemMouseCursors.text,
         onEnter: (PointerEnterEvent event) => _handleHover(true),
         onExit: (PointerExitEvent event) => _handleHover(false),
         child: AnimatedBuilder(
diff --git a/packages/flutter/lib/src/material/toggle_buttons.dart b/packages/flutter/lib/src/material/toggle_buttons.dart
index 0186136..4d3f0a2 100644
--- a/packages/flutter/lib/src/material/toggle_buttons.dart
+++ b/packages/flutter/lib/src/material/toggle_buttons.dart
@@ -166,6 +166,7 @@
     @required this.children,
     @required this.isSelected,
     this.onPressed,
+    this.mouseCursor,
     this.textStyle,
     this.constraints,
     this.color,
@@ -218,6 +219,9 @@
   /// When the callback is null, all toggle buttons will be disabled.
   final void Function(int index) onPressed;
 
+  /// {@macro flutter.material.button.mouseCursor}
+  final MouseCursor mouseCursor;
+
   /// The [TextStyle] to apply to any text in these toggle buttons.
   ///
   /// [TextStyle.color] will be ignored and substituted by [color],
@@ -601,6 +605,7 @@
             onPressed: onPressed != null
               ? () { onPressed(index); }
               : null,
+            mouseCursor: mouseCursor,
             leadingBorderSide: leadingBorderSide,
             horizontalBorderSide: horizontalBorderSide,
             trailingBorderSide: trailingBorderSide,
@@ -667,6 +672,7 @@
     this.splashColor,
     this.focusNode,
     this.onPressed,
+    this.mouseCursor,
     this.leadingBorderSide,
     this.horizontalBorderSide,
     this.trailingBorderSide,
@@ -726,6 +732,9 @@
   /// If this is null, the button will be disabled, see [enabled].
   final VoidCallback onPressed;
 
+  /// {@macro flutter.material.button.mouseCursor}
+  final MouseCursor mouseCursor;
+
   /// The width and color of the button's leading side border.
   final BorderSide leadingBorderSide;
 
@@ -821,6 +830,7 @@
         focusNode: focusNode,
         materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
         onPressed: onPressed,
+        mouseCursor: mouseCursor,
         child: child,
       ),
     );
diff --git a/packages/flutter/lib/src/widgets/actions.dart b/packages/flutter/lib/src/widgets/actions.dart
index d4a52b8..4c2dd95 100644
--- a/packages/flutter/lib/src/widgets/actions.dart
+++ b/packages/flutter/lib/src/widgets/actions.dart
@@ -5,6 +5,7 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/gestures.dart';
+import 'package:flutter/rendering.dart';
 
 import 'basic.dart';
 import 'focus_manager.dart';
@@ -869,7 +870,7 @@
 class FocusableActionDetector extends StatefulWidget {
   /// Create a const [FocusableActionDetector].
   ///
-  /// The [enabled], [autofocus], and [child] arguments must not be null.
+  /// The [enabled], [autofocus], [mouseCursor], and [child] arguments must not be null.
   const FocusableActionDetector({
     Key key,
     this.enabled = true,
@@ -880,9 +881,11 @@
     this.onShowFocusHighlight,
     this.onShowHoverHighlight,
     this.onFocusChange,
+    this.mouseCursor = MouseCursor.defer,
     @required this.child,
   })  : assert(enabled != null),
         assert(autofocus != null),
+        assert(mouseCursor != null),
         assert(child != null),
         super(key: key);
 
@@ -923,6 +926,13 @@
   /// Called with true if the [focusNode] has primary focus.
   final ValueChanged<bool> onFocusChange;
 
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// The [cursor] defaults to [MouseCursor.defer], deferring the choice of
+  /// cursor to the next region behing it in hit-test order.
+  final MouseCursor mouseCursor;
+
   /// The child widget for this [FocusableActionDetector] widget.
   ///
   /// {@macro flutter.widgets.child}
@@ -1073,6 +1083,7 @@
     Widget child = MouseRegion(
       onEnter: _handleMouseEnter,
       onExit: _handleMouseExit,
+      cursor: widget.mouseCursor,
       child: Focus(
         focusNode: widget.focusNode,
         autofocus: widget.autofocus,
diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart
index 081a371..ff29484 100644
--- a/packages/flutter/lib/src/widgets/editable_text.dart
+++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -390,6 +390,7 @@
     this.onSelectionChanged,
     this.onSelectionHandleTapped,
     List<TextInputFormatter> inputFormatters,
+    this.mouseCursor,
     this.rendererIgnoresPointer = false,
     this.cursorWidth = 2.0,
     this.cursorRadius,
@@ -979,6 +980,16 @@
   /// {@endtemplate}
   final List<TextInputFormatter> inputFormatters;
 
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If this property is null, [SystemMouseCursors.text] will be used.
+  ///
+  /// The [mouseCursor] is the only property of [EditableText] that controls the
+  /// mouse pointer. All other properties related to "cursor" stands for the text
+  /// cursor, which is usually a blinking vertical line at the editing position.
+  final MouseCursor mouseCursor;
+
   /// If true, the [RenderEditable] created by this widget will not handle
   /// pointer events, see [renderEditable] and [RenderEditable.ignorePointer].
   ///
@@ -2018,68 +2029,71 @@
     super.build(context); // See AutomaticKeepAliveClientMixin.
 
     final TextSelectionControls controls = widget.selectionControls;
-    return Scrollable(
-      excludeFromSemantics: true,
-      axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
-      controller: _scrollController,
-      physics: widget.scrollPhysics,
-      dragStartBehavior: widget.dragStartBehavior,
-      viewportBuilder: (BuildContext context, ViewportOffset offset) {
-        return CompositedTransformTarget(
-          link: _toolbarLayerLink,
-          child: Semantics(
-            onCopy: _semanticsOnCopy(controls),
-            onCut: _semanticsOnCut(controls),
-            onPaste: _semanticsOnPaste(controls),
-            child: _Editable(
-              key: _editableKey,
-              startHandleLayerLink: _startHandleLayerLink,
-              endHandleLayerLink: _endHandleLayerLink,
-              textSpan: buildTextSpan(),
-              value: _value,
-              cursorColor: _cursorColor,
-              backgroundCursorColor: widget.backgroundCursorColor,
-              showCursor: EditableText.debugDeterministicCursor
-                  ? ValueNotifier<bool>(widget.showCursor)
-                  : _cursorVisibilityNotifier,
-              forceLine: widget.forceLine,
-              readOnly: widget.readOnly,
-              hasFocus: _hasFocus,
-              maxLines: widget.maxLines,
-              minLines: widget.minLines,
-              expands: widget.expands,
-              strutStyle: widget.strutStyle,
-              selectionColor: widget.selectionColor,
-              textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
-              textAlign: widget.textAlign,
-              textDirection: _textDirection,
-              locale: widget.locale,
-              textWidthBasis: widget.textWidthBasis,
-              obscuringCharacter: widget.obscuringCharacter,
-              obscureText: widget.obscureText,
-              autocorrect: widget.autocorrect,
-              smartDashesType: widget.smartDashesType,
-              smartQuotesType: widget.smartQuotesType,
-              enableSuggestions: widget.enableSuggestions,
-              offset: offset,
-              onSelectionChanged: _handleSelectionChanged,
-              onCaretChanged: _handleCaretChanged,
-              rendererIgnoresPointer: widget.rendererIgnoresPointer,
-              cursorWidth: widget.cursorWidth,
-              cursorRadius: widget.cursorRadius,
-              cursorOffset: widget.cursorOffset,
-              selectionHeightStyle: widget.selectionHeightStyle,
-              selectionWidthStyle: widget.selectionWidthStyle,
-              paintCursorAboveText: widget.paintCursorAboveText,
-              enableInteractiveSelection: widget.enableInteractiveSelection,
-              textSelectionDelegate: this,
-              devicePixelRatio: _devicePixelRatio,
-              promptRectRange: _currentPromptRectRange,
-              promptRectColor: widget.autocorrectionTextRectColor,
+    return MouseRegion(
+      cursor: widget.mouseCursor ?? SystemMouseCursors.text,
+      child: Scrollable(
+        excludeFromSemantics: true,
+        axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
+        controller: _scrollController,
+        physics: widget.scrollPhysics,
+        dragStartBehavior: widget.dragStartBehavior,
+        viewportBuilder: (BuildContext context, ViewportOffset offset) {
+          return CompositedTransformTarget(
+            link: _toolbarLayerLink,
+            child: Semantics(
+              onCopy: _semanticsOnCopy(controls),
+              onCut: _semanticsOnCut(controls),
+              onPaste: _semanticsOnPaste(controls),
+              child: _Editable(
+                key: _editableKey,
+                startHandleLayerLink: _startHandleLayerLink,
+                endHandleLayerLink: _endHandleLayerLink,
+                textSpan: buildTextSpan(),
+                value: _value,
+                cursorColor: _cursorColor,
+                backgroundCursorColor: widget.backgroundCursorColor,
+                showCursor: EditableText.debugDeterministicCursor
+                    ? ValueNotifier<bool>(widget.showCursor)
+                    : _cursorVisibilityNotifier,
+                forceLine: widget.forceLine,
+                readOnly: widget.readOnly,
+                hasFocus: _hasFocus,
+                maxLines: widget.maxLines,
+                minLines: widget.minLines,
+                expands: widget.expands,
+                strutStyle: widget.strutStyle,
+                selectionColor: widget.selectionColor,
+                textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
+                textAlign: widget.textAlign,
+                textDirection: _textDirection,
+                locale: widget.locale,
+                textWidthBasis: widget.textWidthBasis,
+                obscuringCharacter: widget.obscuringCharacter,
+                obscureText: widget.obscureText,
+                autocorrect: widget.autocorrect,
+                smartDashesType: widget.smartDashesType,
+                smartQuotesType: widget.smartQuotesType,
+                enableSuggestions: widget.enableSuggestions,
+                offset: offset,
+                onSelectionChanged: _handleSelectionChanged,
+                onCaretChanged: _handleCaretChanged,
+                rendererIgnoresPointer: widget.rendererIgnoresPointer,
+                cursorWidth: widget.cursorWidth,
+                cursorRadius: widget.cursorRadius,
+                cursorOffset: widget.cursorOffset,
+                selectionHeightStyle: widget.selectionHeightStyle,
+                selectionWidthStyle: widget.selectionWidthStyle,
+                paintCursorAboveText: widget.paintCursorAboveText,
+                enableInteractiveSelection: widget.enableInteractiveSelection,
+                textSelectionDelegate: this,
+                devicePixelRatio: _devicePixelRatio,
+                promptRectRange: _currentPromptRectRange,
+                promptRectColor: widget.autocorrectionTextRectColor,
+              ),
             ),
-          ),
-        );
-      },
+          );
+        },
+      ),
     );
   }
 
diff --git a/packages/flutter/lib/src/widgets/modal_barrier.dart b/packages/flutter/lib/src/widgets/modal_barrier.dart
index 24e0002..b83aa18 100644
--- a/packages/flutter/lib/src/widgets/modal_barrier.dart
+++ b/packages/flutter/lib/src/widgets/modal_barrier.dart
@@ -4,6 +4,7 @@
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
+import 'package:flutter/rendering.dart';
 
 import 'basic.dart';
 import 'container.dart';
@@ -105,6 +106,7 @@
             label: semanticsDismissible ? semanticsLabel : null,
             textDirection: semanticsDismissible && semanticsLabel != null ? Directionality.of(context) : null,
             child: MouseRegion(
+              cursor: SystemMouseCursors.basic,
               opaque: true,
               child: ConstrainedBox(
                 constraints: const BoxConstraints.expand(),
diff --git a/packages/flutter/test/material/bottom_navigation_bar_test.dart b/packages/flutter/test/material/bottom_navigation_bar_test.dart
index 793611e..d4ff1cf 100644
--- a/packages/flutter/test/material/bottom_navigation_bar_test.dart
+++ b/packages/flutter/test/material/bottom_navigation_bar_test.dart
@@ -1661,6 +1661,52 @@
     semantics.dispose();
   });
 
+  testWidgets('BottomNavigationBar changes mouse cursor when the tile is hovered over', (WidgetTester tester) async {
+    // Test BottomNavigationBar() constructor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          bottomNavigationBar: MouseRegion(
+            cursor: SystemMouseCursors.forbidden,
+            child: BottomNavigationBar(
+              mouseCursor: SystemMouseCursors.text,
+              items: const <BottomNavigationBarItem>[
+                BottomNavigationBarItem(icon: Icon(Icons.ac_unit), title: Text('AC')),
+                BottomNavigationBarItem(icon: Icon(Icons.access_alarm), title: Text('Alarm')),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.text('AC')));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          bottomNavigationBar: MouseRegion(
+            cursor: SystemMouseCursors.forbidden,
+            child: BottomNavigationBar(
+              items: const <BottomNavigationBarItem>[
+                BottomNavigationBarItem(icon: Icon(Icons.ac_unit), title: Text('AC')),
+                BottomNavigationBarItem(icon: Icon(Icons.access_alarm), title: Text('Alarm')),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+  });
 }
 
 Widget boilerplate({ Widget bottomNavigationBar, @required TextDirection textDirection }) {
diff --git a/packages/flutter/test/material/checkbox_test.dart b/packages/flutter/test/material/checkbox_test.dart
index 3b60092..33b3f63 100644
--- a/packages/flutter/test/material/checkbox_test.dart
+++ b/packages/flutter/test/material/checkbox_test.dart
@@ -602,4 +602,120 @@
     await tester.pumpAndSettle();
     expect(box.size, equals(const Size(60, 36)));
   });
+
+  testWidgets('Checkbox changes mouse cursor when hovered', (WidgetTester tester) async {
+    // Test Checkbox() constructor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Checkbox(
+                  mouseCursor: SystemMouseCursors.text,
+                  value: true,
+                  onChanged: (_) {},
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.byType(Checkbox)));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Checkbox(
+                  value: true,
+                  onChanged: (_) {},
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor when disabled
+    await tester.pumpWidget(
+      const MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Checkbox(
+                  value: true,
+                  onChanged: null,
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+
+    // Test cursor when tristate
+    await tester.pumpWidget(
+      const MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Checkbox(
+                  value: null,
+                  tristate: true,
+                  onChanged: null,
+                  mouseCursor: _SelectedGrabMouseCursor(),
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.grab);
+
+    await tester.pumpAndSettle();
+  });
+}
+
+class _SelectedGrabMouseCursor extends MaterialStateMouseCursor {
+  const _SelectedGrabMouseCursor();
+
+  @override
+  MouseCursor resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.selected)) {
+      return SystemMouseCursors.grab;
+    }
+    return SystemMouseCursors.basic;
+  }
+
+  @override
+  String get debugDescription => '_SelectedGrabMouseCursor()';
 }
diff --git a/packages/flutter/test/material/flat_button_test.dart b/packages/flutter/test/material/flat_button_test.dart
index 6e8f342..13c5f2e 100644
--- a/packages/flutter/test/material/flat_button_test.dart
+++ b/packages/flutter/test/material/flat_button_test.dart
@@ -437,6 +437,78 @@
     await gesture.removePointer();
   });
 
+  testWidgets('FlatButton changes mouse cursor when hovered', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: FlatButton.icon(
+            icon: const Icon(Icons.add),
+            label: const Text('Hello'),
+            onPressed: () {},
+            mouseCursor: SystemMouseCursors.text,
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: const Offset(1, 1));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: FlatButton(
+            onPressed: () {},
+            mouseCursor: SystemMouseCursors.text,
+            child: const Text('Hello'),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: FlatButton(
+            onPressed: () {},
+            child: const Text('Hello'),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor when disabled
+    await tester.pumpWidget(
+      const Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: FlatButton(
+            onPressed: null,
+            child: Text('Hello'),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+  });
+
   testWidgets('Does FlatButton work with focus', (WidgetTester tester) async {
     const Color focusColor = Color(0xff001122);
 
diff --git a/packages/flutter/test/material/floating_action_button_test.dart b/packages/flutter/test/material/floating_action_button_test.dart
index d2d6f22..9193935 100644
--- a/packages/flutter/test/material/floating_action_button_test.dart
+++ b/packages/flutter/test/material/floating_action_button_test.dart
@@ -744,6 +744,84 @@
     );
   });
 
+  testWidgets('Floating Action Button changes mouse cursor when hovered', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: FloatingActionButton.extended(
+              onPressed: () { },
+              mouseCursor: SystemMouseCursors.text,
+              label: const Text('label'),
+              icon: const Icon(Icons.android),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.byType(FloatingActionButton)));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: FloatingActionButton(
+              onPressed: () { },
+              mouseCursor: SystemMouseCursors.text,
+              child: const Icon(Icons.add),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    await gesture.moveTo(tester.getCenter(find.byType(FloatingActionButton)));
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: FloatingActionButton(
+              onPressed: () { },
+              child: const Icon(Icons.add),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor when disabled
+    await tester.pumpWidget(
+      const MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: FloatingActionButton(
+              onPressed: null,
+              child: Icon(Icons.add),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+  });
+
   testWidgets('Floating Action Button has no clip by default', (WidgetTester tester) async {
     final FocusNode focusNode = FocusNode();
     await tester.pumpWidget(
diff --git a/packages/flutter/test/material/icon_button_test.dart b/packages/flutter/test/material/icon_button_test.dart
index a699404..d582748 100644
--- a/packages/flutter/test/material/icon_button_test.dart
+++ b/packages/flutter/test/material/icon_button_test.dart
@@ -638,6 +638,49 @@
     await tester.pumpAndSettle();
     expect(box.size, equals(const Size(60, 40)));
   });
+
+  testWidgets('IconButton.mouseCursor changes cursor on hover', (WidgetTester tester) async {
+    // Test argument works
+    await tester.pumpWidget(
+      Material(
+        child: Directionality(
+          textDirection: TextDirection.ltr,
+          child: Center(
+            child: IconButton(
+              onPressed: () {},
+              mouseCursor: SystemMouseCursors.forbidden,
+              icon: const Icon(Icons.play_arrow),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.byType(IconButton)));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
+
+    // Test default is click
+    await tester.pumpWidget(
+      Material(
+        child: Directionality(
+          textDirection: TextDirection.ltr,
+          child: Center(
+            child: IconButton(
+              onPressed: () {},
+              icon: const Icon(Icons.play_arrow),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+  });
 }
 
 Widget wrap({ Widget child }) {
diff --git a/packages/flutter/test/material/ink_well_test.dart b/packages/flutter/test/material/ink_well_test.dart
index 7ad5d9e..bd957c9 100644
--- a/packages/flutter/test/material/ink_well_test.dart
+++ b/packages/flutter/test/material/ink_well_test.dart
@@ -189,6 +189,64 @@
     expect(inkFeatures, paintsExactlyCountTimes(#rect, 0));
   });
 
+  testWidgets('InkWell.mouseCursor changes cursor on hover', (WidgetTester tester) async {
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: const Offset(1, 1));
+    addTearDown(gesture.removePointer);
+
+    // Test argument works
+    await tester.pumpWidget(
+      Material(
+        child: Directionality(
+          textDirection: TextDirection.ltr,
+          child: MouseRegion(
+            cursor: SystemMouseCursors.forbidden,
+            child: InkWell(
+              mouseCursor: SystemMouseCursors.click,
+              onTap: () {},
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default of InkWell()
+    await tester.pumpWidget(
+      Material(
+        child: Directionality(
+          textDirection: TextDirection.ltr,
+          child: MouseRegion(
+            cursor: SystemMouseCursors.forbidden,
+            child: InkWell(
+              onTap: () {},
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
+
+    // Test default of InkResponse()
+    await tester.pumpWidget(
+      Material(
+        child: Directionality(
+          textDirection: TextDirection.ltr,
+          child: MouseRegion(
+            cursor: SystemMouseCursors.forbidden,
+            child: InkResponse(
+              onTap: () {},
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
+  });
+
   group('feedback', () {
     FeedbackTester feedback;
 
diff --git a/packages/flutter/test/material/list_tile_test.dart b/packages/flutter/test/material/list_tile_test.dart
index f91c98d..37ab63f 100644
--- a/packages/flutter/test/material/list_tile_test.dart
+++ b/packages/flutter/test/material/list_tile_test.dart
@@ -1443,4 +1443,67 @@
     await tester.pumpAndSettle();
     expect(box.size, equals(const Size(800, 44)));
   });
+
+  testWidgets('ListTile changes mouse cursor when hovered', (WidgetTester tester) async {
+    // Test ListTile() constructor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Material(
+          child: Center(
+            child: MouseRegion(
+              cursor: SystemMouseCursors.forbidden,
+              child: ListTile(
+                onTap: () {},
+                mouseCursor: SystemMouseCursors.text,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.byType(ListTile)));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Material(
+          child: Center(
+            child: MouseRegion(
+              cursor: SystemMouseCursors.forbidden,
+              child: ListTile(
+                onTap: () {},
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor when disabled
+    await tester.pumpWidget(
+      const MaterialApp(
+        home: Material(
+          child: Center(
+            child: MouseRegion(
+              cursor: SystemMouseCursors.forbidden,
+              child: ListTile(
+                enabled: false,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+  });
 }
diff --git a/packages/flutter/test/material/material_button_test.dart b/packages/flutter/test/material/material_button_test.dart
index 7bd183e..58d3cac 100644
--- a/packages/flutter/test/material/material_button_test.dart
+++ b/packages/flutter/test/material/material_button_test.dart
@@ -373,6 +373,59 @@
     expect(didLongPressButton, isTrue);
   });
 
+  testWidgets('MaterialButton changes mouse cursor when hovered', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: MaterialButton(
+            onPressed: () {},
+            mouseCursor: SystemMouseCursors.text,
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: Offset.zero);
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: MaterialButton(
+            onPressed: () {},
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor when disabled
+    await tester.pumpWidget(
+      const Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: MaterialButton(
+            onPressed: null,
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+  });
+
   // This test is very similar to the '...explicit splashColor and highlightColor' test
   // in icon_button_test.dart. If you change this one, you may want to also change that one.
   testWidgets('MaterialButton with explicit splashColor and highlightColor', (WidgetTester tester) async {
diff --git a/packages/flutter/test/material/outline_button_test.dart b/packages/flutter/test/material/outline_button_test.dart
index ea40ce2..8a69adb 100644
--- a/packages/flutter/test/material/outline_button_test.dart
+++ b/packages/flutter/test/material/outline_button_test.dart
@@ -109,6 +109,75 @@
     gesture.removePointer();
   });
 
+  testWidgets('OutlineButton changes mouse cursor when hovered', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: OutlineButton.icon(
+            icon: const Icon(Icons.add),
+            label: const Text('Hello'),
+            onPressed: () {},
+            mouseCursor: SystemMouseCursors.text,
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: const Offset(1, 1));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: OutlineButton(
+            onPressed: () {},
+            mouseCursor: SystemMouseCursors.text,
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: OutlineButton(
+            onPressed: () {},
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor when disabled
+    await tester.pumpWidget(
+      const Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: OutlineButton(
+            onPressed: null,
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+  });
+
   testWidgets('Does OutlineButton work with focus', (WidgetTester tester) async {
     const Color focusColor = Color(0xff001122);
 
diff --git a/packages/flutter/test/material/popup_menu_test.dart b/packages/flutter/test/material/popup_menu_test.dart
index 58d1c1d..70b6139 100644
--- a/packages/flutter/test/material/popup_menu_test.dart
+++ b/packages/flutter/test/material/popup_menu_test.dart
@@ -5,6 +5,8 @@
 import 'dart:ui' show window, SemanticsFlag;
 
 import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/rendering.dart';
 import 'package:flutter/material.dart';
 
 import '../widgets/semantics_tester.dart';
@@ -1303,6 +1305,86 @@
 
     expect(find.text('Tap me please!'), findsOneWidget);
   });
+
+  testWidgets('PopupMenuItem changes mouse cursor when hovered', (WidgetTester tester) async {
+    const Key key = ValueKey<int>(1);
+    // Test PopupMenuItem() constructor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: PopupMenuItem<int>(
+                  key: key,
+                  mouseCursor: SystemMouseCursors.text,
+                  value: 1,
+                  child: Container(),
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.byKey(key)));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: PopupMenuItem<int>(
+                  key: key,
+                  value: 1,
+                  child: Container(),
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor when disabled
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: PopupMenuItem<int>(
+                  key: key,
+                  value: 1,
+                  enabled: false,
+                  child: Container(),
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+  });
 }
 
 class TestApp extends StatefulWidget {
diff --git a/packages/flutter/test/material/radio_test.dart b/packages/flutter/test/material/radio_test.dart
index 3524dbd..ef7a378 100644
--- a/packages/flutter/test/material/radio_test.dart
+++ b/packages/flutter/test/material/radio_test.dart
@@ -613,4 +613,85 @@
     await tester.pumpAndSettle();
     expect(box.size, equals(const Size(60, 36)));
   });
+
+  testWidgets('Radio changes mouse cursor when hovered', (WidgetTester tester) async {
+    const Key key = ValueKey<int>(1);
+    // Test Radio() constructor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Radio<int>(
+                  key: key,
+                  mouseCursor: SystemMouseCursors.text,
+                  value: 1,
+                  onChanged: (int v) {},
+                  groupValue: 2,
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.byKey(key)));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+
+    // Test default cursor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Radio<int>(
+                  value: 1,
+                  onChanged: (int v) {},
+                  groupValue: 2,
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor when disabled
+    await tester.pumpWidget(
+      const MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Radio<int>(
+                  value: 1,
+                  onChanged: null,
+                  groupValue: 2,
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+  });
 }
diff --git a/packages/flutter/test/material/raised_button_test.dart b/packages/flutter/test/material/raised_button_test.dart
index cca0418..6bdd01c 100644
--- a/packages/flutter/test/material/raised_button_test.dart
+++ b/packages/flutter/test/material/raised_button_test.dart
@@ -431,6 +431,76 @@
     await gesture.removePointer();
   });
 
+  testWidgets('RaisedButton changes mouse cursor when hovered', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: RaisedButton.icon(
+            icon: const Icon(Icons.add),
+            label: const Text('Hello'),
+            onPressed: () {},
+            mouseCursor: SystemMouseCursors.text,
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: const Offset(1, 1));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: RaisedButton(
+            onPressed: () {},
+            mouseCursor: SystemMouseCursors.text,
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: RaisedButton(
+            onPressed: () {},
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor when disabled
+    await tester.pumpWidget(
+      const Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: RaisedButton(
+            onPressed: null,
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+  });
+
+
   testWidgets('Does RaisedButton work with focus', (WidgetTester tester) async {
     const Color focusColor = Color(0xff001122);
 
diff --git a/packages/flutter/test/material/raw_material_button_test.dart b/packages/flutter/test/material/raw_material_button_test.dart
index 0efcc87..9047840 100644
--- a/packages/flutter/test/material/raw_material_button_test.dart
+++ b/packages/flutter/test/material/raw_material_button_test.dart
@@ -570,4 +570,57 @@
     expect(box.size, equals(const Size(76, 36)));
     expect(childRect, equals(const Rect.fromLTRB(372.0, 293.0, 428.0, 307.0)));
   });
+
+  testWidgets('RawMaterialButton changes mouse cursor when hovered', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: RawMaterialButton(
+            onPressed: () {},
+            mouseCursor: SystemMouseCursors.text,
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: Offset.zero);
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: RawMaterialButton(
+            onPressed: () {},
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor when disabled
+    await tester.pumpWidget(
+      const Directionality(
+        textDirection: TextDirection.ltr,
+        child: MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: RawMaterialButton(
+            onPressed: null,
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+  });
 }
diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart
index 5445347..6aa83ca 100644
--- a/packages/flutter/test/material/slider_test.dart
+++ b/packages/flutter/test/material/slider_test.dart
@@ -2136,6 +2136,82 @@
     expect(renderObject.size.height, 200);
   });
 
+  testWidgets('Slider changes mouse cursor when hovered', (WidgetTester tester) async {
+    // Test Slider() constructor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Directionality(
+          textDirection: TextDirection.ltr,
+          child: Material(
+            child: Center(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Slider(
+                  mouseCursor: SystemMouseCursors.text,
+                  value: 0.5,
+                  onChanged: (double newValue) { },
+                ),
+              ),
+            ),
+          ),
+        ),
+      )
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.byType(Slider)));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test Slider.adaptive() constructor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Directionality(
+          textDirection: TextDirection.ltr,
+          child: Material(
+            child: Center(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Slider.adaptive(
+                  mouseCursor: SystemMouseCursors.text,
+                  value: 0.5,
+                  onChanged: (double newValue) { },
+                ),
+              ),
+            ),
+          ),
+        ),
+      )
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Directionality(
+          textDirection: TextDirection.ltr,
+          child: Material(
+            child: Center(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Slider(
+                  value: 0.5,
+                  onChanged: (double newValue) { },
+                ),
+              ),
+            ),
+          ),
+        ),
+      )
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+  });
+
   testWidgets('Slider implements debugFillProperties', (WidgetTester tester) async {
     final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
 
diff --git a/packages/flutter/test/material/switch_test.dart b/packages/flutter/test/material/switch_test.dart
index 6358654..3361e34 100644
--- a/packages/flutter/test/material/switch_test.dart
+++ b/packages/flutter/test/material/switch_test.dart
@@ -912,4 +912,105 @@
     await tester.pumpAndSettle();
     expect(value, isTrue);
   });
+
+  testWidgets('Switch changes mouse cursor when hovered', (WidgetTester tester) async {
+    // Test Switch.adaptive() constructor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Switch.adaptive(
+                  mouseCursor: SystemMouseCursors.text,
+                  value: true,
+                  onChanged: (_) {},
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.byType(Switch)));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test Switch() constructor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Switch(
+                  mouseCursor: SystemMouseCursors.text,
+                  value: true,
+                  onChanged: (_) {},
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    await gesture.moveTo(tester.getCenter(find.byType(Switch)));
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Switch(
+                  value: true,
+                  onChanged: (_) {},
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor when disabled
+    await tester.pumpWidget(
+      const MaterialApp(
+        home: Scaffold(
+          body: Align(
+            alignment: Alignment.topLeft,
+            child: Material(
+              child: MouseRegion(
+                cursor: SystemMouseCursors.forbidden,
+                child: Switch(
+                  value: true,
+                  onChanged: null,
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+
+    await tester.pumpAndSettle();
+  });
 }
diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart
index b4ec181..098366d 100644
--- a/packages/flutter/test/material/tabs_test.dart
+++ b/packages/flutter/test/material/tabs_test.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter/rendering.dart';
@@ -2028,6 +2029,45 @@
     expect(() => Tab(text: 'foo', child: Container()), throwsAssertionError);
   });
 
+  testWidgets('Tabs changes mouse cursor when a tab is hovered', (WidgetTester tester) async {
+    final List<String> tabs = <String>['A', 'B'];
+    await tester.pumpWidget(MaterialApp(home: DefaultTabController(
+        length: tabs.length,
+        child: Scaffold(
+          body: MouseRegion(
+            cursor: SystemMouseCursors.forbidden,
+            child: TabBar(
+              mouseCursor: SystemMouseCursors.text,
+              tabs: tabs.map<Widget>((String tab) => Tab(text: tab)).toList(),
+            ),
+          ),
+        ),
+      ),
+    ));
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.byType(Tab).first));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(MaterialApp(home: DefaultTabController(
+        length: tabs.length,
+        child: Scaffold(
+          body: MouseRegion(
+            cursor: SystemMouseCursors.forbidden,
+            child: TabBar(
+              tabs: tabs.map<Widget>((String tab) => Tab(text: tab)).toList(),
+            ),
+          ),
+        ),
+      ),
+    ));
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+  });
 
   testWidgets('TabController changes', (WidgetTester tester) async {
     // This is a regression test for https://github.com/flutter/flutter/issues/14812
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index 0e69b32..9e1b009 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -7788,4 +7788,31 @@
       expect(triedToReadClipboard, true);
     }
   });
+
+  testWidgets('TextField changes mouse cursor when hovered', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      const MaterialApp(
+        home: Material(
+          child: TextField(
+            decoration: InputDecoration(
+              // Add an icon so that the left edge is not the text area
+              icon: Icon(Icons.person),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.byType(TextField)));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test top left, which is not the text area
+    await gesture.moveTo(tester.getTopLeft(find.byType(TextField)) + const Offset(1, 1));
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+  });
 }
diff --git a/packages/flutter/test/material/toggle_buttons_test.dart b/packages/flutter/test/material/toggle_buttons_test.dart
index 791df6d..992445b 100644
--- a/packages/flutter/test/material/toggle_buttons_test.dart
+++ b/packages/flutter/test/material/toggle_buttons_test.dart
@@ -4,6 +4,7 @@
 
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
 import 'package:flutter_test/flutter_test.dart';
 
 import '../rendering/mock_canvas.dart';
@@ -1434,4 +1435,74 @@
       );
     },
   );
+
+  testWidgets('ToggleButtons changes mouse cursor when the button is hovered', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      Material(
+        child: boilerplate(
+          child: MouseRegion(
+            cursor: SystemMouseCursors.forbidden,
+            child: ToggleButtons(
+              mouseCursor: SystemMouseCursors.text,
+              onPressed: (int index) {},
+              isSelected: const <bool>[false, true],
+              children: const <Widget>[
+                Text('First child'),
+                Text('Second child'),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.text('First child')));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      Material(
+        child: boilerplate(
+          child: MouseRegion(
+            cursor: SystemMouseCursors.forbidden,
+            child: ToggleButtons(
+              onPressed: (int index) {},
+              isSelected: const <bool>[false, true],
+              children: const <Widget>[
+                Text('First child'),
+                Text('Second child'),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor when disabled
+    await tester.pumpWidget(
+      Material(
+        child: boilerplate(
+          child: MouseRegion(
+            cursor: SystemMouseCursors.forbidden,
+            child: ToggleButtons(
+              isSelected: const <bool>[false, true],
+              children: const <Widget>[
+                Text('First child'),
+                Text('Second child'),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+  });
 }
diff --git a/packages/flutter/test/widgets/actions_test.dart b/packages/flutter/test/widgets/actions_test.dart
index 82e1a98..78ef491 100644
--- a/packages/flutter/test/widgets/actions_test.dart
+++ b/packages/flutter/test/widgets/actions_test.dart
@@ -5,6 +5,7 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter_test/flutter_test.dart';
@@ -317,6 +318,109 @@
       expect(() => Actions.find<DoNothingIntent>(containerKey.currentContext), throwsAssertionError);
       expect(Actions.find<DoNothingIntent>(containerKey.currentContext, nullOk: true), isNull);
     });
+    testWidgets('FocusableActionDetector keeps track of focus and hover even when disabled.', (WidgetTester tester) async {
+      FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
+      final GlobalKey containerKey = GlobalKey();
+      bool invoked = false;
+      const Intent intent = TestIntent();
+      final FocusNode focusNode = FocusNode(debugLabel: 'Test Node');
+      final Action<Intent> testAction = TestAction(
+        onInvoke: (Intent intent) {
+          invoked = true;
+          return invoked;
+        },
+      );
+      bool hovering = false;
+      bool focusing = false;
+
+      Future<void> buildTest(bool enabled) async {
+        await tester.pumpWidget(
+          Center(
+            child: Actions(
+              dispatcher: TestDispatcher1(postInvoke: collect),
+              actions: const <Type, Action<Intent>>{},
+              child: FocusableActionDetector(
+                enabled: enabled,
+                focusNode: focusNode,
+                shortcuts: <LogicalKeySet, Intent>{
+                  LogicalKeySet(LogicalKeyboardKey.enter): intent,
+                },
+                actions: <Type, Action<Intent>>{
+                  TestIntent: testAction,
+                },
+                onShowHoverHighlight: (bool value) => hovering = value,
+                onShowFocusHighlight: (bool value) => focusing = value,
+                child: Container(width: 100, height: 100, key: containerKey),
+              ),
+            ),
+          ),
+        );
+        return tester.pump();
+      }
+
+      await buildTest(true);
+      focusNode.requestFocus();
+      await tester.pump();
+      final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
+      addTearDown(gesture.removePointer);
+      await gesture.moveTo(tester.getCenter(find.byKey(containerKey)));
+      await tester.pump();
+      await tester.sendKeyEvent(LogicalKeyboardKey.enter);
+      expect(hovering, isTrue);
+      expect(focusing, isTrue);
+      expect(invoked, isTrue);
+
+      invoked = false;
+      await buildTest(false);
+      expect(hovering, isFalse);
+      expect(focusing, isFalse);
+      await tester.sendKeyEvent(LogicalKeyboardKey.enter);
+      await tester.pump();
+      expect(invoked, isFalse);
+      await buildTest(true);
+      expect(focusing, isFalse);
+      expect(hovering, isTrue);
+      await buildTest(false);
+      expect(focusing, isFalse);
+      expect(hovering, isFalse);
+      await gesture.moveTo(Offset.zero);
+      await buildTest(true);
+      expect(hovering, isFalse);
+      expect(focusing, isFalse);
+    });
+    testWidgets('FocusableActionDetector changes mouse cursor when hovered', (WidgetTester tester) async {
+      await tester.pumpWidget(
+        MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: FocusableActionDetector(
+            mouseCursor: SystemMouseCursors.text,
+            onShowHoverHighlight: (_) {},
+            onShowFocusHighlight: (_) {},
+            child: Container(),
+          ),
+        ),
+      );
+      final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+      await gesture.addPointer(location: const Offset(1, 1));
+      addTearDown(gesture.removePointer);
+      await tester.pump();
+
+      expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+
+      // Test default
+      await tester.pumpWidget(
+        MouseRegion(
+          cursor: SystemMouseCursors.forbidden,
+          child: FocusableActionDetector(
+            onShowHoverHighlight: (_) {},
+            onShowFocusHighlight: (_) {},
+            child: Container(),
+          ),
+        ),
+      );
+
+      expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
+    });
   });
 
   group('Listening', () {
diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart
index 1093db8..05c4c8e 100644
--- a/packages/flutter/test/widgets/editable_text_test.dart
+++ b/packages/flutter/test/widgets/editable_text_test.dart
@@ -6,6 +6,7 @@
 
 import 'package:flutter/rendering.dart';
 import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/widgets.dart';
@@ -4702,6 +4703,64 @@
     state.updateEditingValue(const TextEditingValue(text: '\u{200E}🇧🇼🇧🇷🇮🇴 🇻🇬🇧🇳wahhh!🇧🇬🇧🇫 🇧🇮🇰🇭عَ عَ 🇨🇲 🇨🇦🇮🇨 🇨🇻🇧🇶 🇰🇾🇨🇫 🇹🇩🇨🇱 🇨🇳🇨🇽\u{200F}'));
     expect(state.currentTextEditingValue.text, equals('\u{200E}🇧🇼🇧🇷🇮🇴 🇻🇬🇧🇳wahhh!🇧🇬🇧🇫 🇧🇮🇰🇭عَ عَ \u{200F}🇨🇲 🇨🇦🇮🇨 🇨🇻🇧🇶 🇰🇾🇨🇫 🇹🇩🇨🇱 🇨🇳🇨🇽\u{200F}'));
   });
+
+  testWidgets('EditableText changes mouse cursor when hovered', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      MediaQuery(
+        data: const MediaQueryData(devicePixelRatio: 1.0),
+        child: Directionality(
+          textDirection: TextDirection.ltr,
+          child: FocusScope(
+            node: focusScopeNode,
+            child: MouseRegion(
+              cursor: SystemMouseCursors.forbidden,
+              child: EditableText(
+                controller: controller,
+                backgroundCursorColor: Colors.grey,
+                focusNode: focusNode,
+                style: textStyle,
+                cursorColor: cursorColor,
+                mouseCursor: SystemMouseCursors.click,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.byType(EditableText)));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
+
+    // Test default cursor
+    await tester.pumpWidget(
+      MediaQuery(
+        data: const MediaQueryData(devicePixelRatio: 1.0),
+        child: Directionality(
+          textDirection: TextDirection.ltr,
+          child: FocusScope(
+            node: focusScopeNode,
+            child: MouseRegion(
+              cursor: SystemMouseCursors.forbidden,
+              child: EditableText(
+                controller: controller,
+                backgroundCursorColor: Colors.grey,
+                focusNode: focusNode,
+                style: textStyle,
+                cursorColor: cursorColor,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+  });
 }
 
 class MockTextFormatter extends TextInputFormatter {
diff --git a/packages/flutter/test/widgets/modal_barrier_test.dart b/packages/flutter/test/widgets/modal_barrier_test.dart
index c501933..7a020a0 100644
--- a/packages/flutter/test/widgets/modal_barrier_test.dart
+++ b/packages/flutter/test/widgets/modal_barrier_test.dart
@@ -373,6 +373,24 @@
 
     semantics.dispose();
   });
+
+  testWidgets('ModalBarrier uses default mouse cursor', (WidgetTester tester) async {
+    await tester.pumpWidget(Stack(
+      textDirection: TextDirection.ltr,
+      children: const <Widget>[
+        MouseRegion(cursor: SystemMouseCursors.click),
+        ModalBarrier(dismissible: false),
+      ],
+    ));
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.byType(ModalBarrier)));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
+  });
 }
 
 class FirstWidget extends StatelessWidget {
diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart
index 979749a..2df512d 100644
--- a/packages/flutter/test/widgets/selectable_text_test.dart
+++ b/packages/flutter/test/widgets/selectable_text_test.dart
@@ -3844,4 +3844,24 @@
     // Long press triggers gesture recognizer.
     expect(spyLongPress, 1);
   });
+
+  testWidgets('SelectableText changes mouse cursor when hovered', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      const MaterialApp(
+        home: Material(
+          child: Center(
+            child: SelectableText('test'),
+          ),
+        ),
+      ),
+    );
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
+    await gesture.addPointer(location: tester.getCenter(find.text('test')));
+    addTearDown(gesture.removePointer);
+
+    await tester.pump();
+
+    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
+  });
 }