Add mouse cursor property to CupertinoRadio (#149681)

Adds `mouseCursor` property in `Radio` to `CupertinoRadio` and `Radio.adaptive`.

The `mouseCursor` property added is of type `MouseCursor` and not `WidgetStateProperty<MouseCursor>` to match `Radio`'s `mouseCursor`.
This commit is contained in:
Victor Sanni 2024-06-12 10:45:07 -07:00 committed by GitHub
parent b1f9d7131c
commit 3db2ece735
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 160 additions and 1 deletions

View File

@ -71,6 +71,7 @@ class CupertinoRadio<T> extends StatefulWidget {
required this.value,
required this.groupValue,
required this.onChanged,
this.mouseCursor,
this.toggleable = false,
this.activeColor,
this.inactiveColor,
@ -121,6 +122,28 @@ class CupertinoRadio<T> extends StatefulWidget {
/// ```
final ValueChanged<T?>? onChanged;
/// The cursor for a mouse pointer when it enters or is hovering over the
/// widget.
///
/// If [mouseCursor] is a [WidgetStateMouseCursor],
/// [WidgetStateMouseCursor.resolve] is used for the following [WidgetState]s:
///
/// * [WidgetState.selected].
/// * [WidgetState.hovered].
/// * [WidgetState.focused].
/// * [WidgetState.disabled].
///
/// If null, then [SystemMouseCursors.basic] is used when this radio button is disabled.
/// When this radio button is enabled, [SystemMouseCursors.click] is used on Web, and
/// [SystemMouseCursors.basic] is used on other platforms.
///
/// See also:
///
/// * [WidgetStateMouseCursor], a [MouseCursor] that implements
/// `WidgetStateProperty` which is used in APIs that need to accept
/// either a [MouseCursor] or a [WidgetStateProperty<MouseCursor>].
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.
///
@ -239,6 +262,15 @@ class _CupertinoRadioState<T> extends State<CupertinoRadio<T>> with TickerProvid
final Color effectiveFillColor = widget.fillColor ?? CupertinoColors.white;
final WidgetStateProperty<MouseCursor> effectiveMouseCursor =
WidgetStateProperty.resolveWith<MouseCursor>((Set<WidgetState> states) {
return WidgetStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, states)
?? (states.contains(WidgetState.disabled)
? SystemMouseCursors.basic
: kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic
);
});
final bool? accessibilitySelected;
// Apple devices also use `selected` to annotate radio button's semantics
// state.
@ -258,6 +290,7 @@ class _CupertinoRadioState<T> extends State<CupertinoRadio<T>> with TickerProvid
checked: widget._selected,
selected: accessibilitySelected,
child: buildToggleable(
mouseCursor: effectiveMouseCursor,
focusNode: widget.focusNode,
autofocus: widget.autofocus,
onFocusChange: onFocusChange,
@ -309,7 +342,6 @@ class _RadioPainter extends ToggleablePainter {
@override
void paint(Canvas canvas, Size size) {
final Offset center = (Offset.zero & size).center;
final Paint paint = Paint()

View File

@ -447,6 +447,7 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg
value: widget.value,
groupValue: widget.groupValue,
onChanged: widget.onChanged,
mouseCursor: widget.mouseCursor,
toggleable: widget.toggleable,
activeColor: widget.activeColor,
focusColor: widget.focusColor,

View File

@ -4,6 +4,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
@ -428,4 +429,129 @@ void main() {
// Release pointer after widget disappeared.
await gesture.up();
});
testWidgets('Radio configures mouse cursor', (WidgetTester tester) async {
await tester.pumpWidget(CupertinoApp(
home: Center(
child: CupertinoRadio<int>(
value: 1,
groupValue: 1,
onChanged: (int? i) { },
mouseCursor: SystemMouseCursors.forbidden,
),
),
));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
pointer: 1
);
addTearDown(gesture.removePointer);
await gesture.addPointer(location: tester.getCenter(find.byType(CupertinoRadio<int>)));
await tester.pump();
await gesture.moveTo(tester.getCenter(find.byType(CupertinoRadio<int>)));
expect(
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
SystemMouseCursors.forbidden
);
});
testWidgets('Mouse cursor resolves in disabled/hovered/focused states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Radio');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
await tester.pumpWidget(CupertinoApp(
home: Center(
child: CupertinoRadio<int>(
value: 1,
groupValue: 1,
onChanged: (int? i) { },
mouseCursor: const RadioMouseCursor(),
focusNode: focusNode
),
),
));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
pointer: 1
);
addTearDown(gesture.removePointer);
await gesture.addPointer(location: tester.getCenter(find.byType(CupertinoRadio<int>)));
await tester.pump();
// Test hovered case.
await gesture.moveTo(tester.getCenter(find.byType(CupertinoRadio<int>)));
expect(
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
SystemMouseCursors.click
);
// Test focused case.
focusNode.requestFocus();
await tester.pump();
expect(
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
SystemMouseCursors.basic
);
// Test disabled case.
await tester.pumpWidget(const CupertinoApp(
home: Center(
child: CupertinoRadio<int>(
value: 1,
groupValue: 1,
onChanged: null,
mouseCursor: RadioMouseCursor(),
),
),
));
await tester.pump();
expect(
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
SystemMouseCursors.forbidden
);
focusNode.dispose();
});
testWidgets('Radio default mouse cursor', (WidgetTester tester) async {
await tester.pumpWidget(CupertinoApp(
home: Center(
child: CupertinoRadio<int>(
value: 1,
groupValue: 1,
onChanged: (int? i) { },
),
),
));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
pointer: 1
);
addTearDown(gesture.removePointer);
await gesture.addPointer(location: tester.getCenter(find.byType(CupertinoRadio<int>)));
await tester.pump();
await gesture.moveTo(tester.getCenter(find.byType(CupertinoRadio<int>)));
expect(
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic
);
});
}
class RadioMouseCursor extends WidgetStateMouseCursor {
const RadioMouseCursor();
@override
MouseCursor resolve(Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
return SystemMouseCursors.forbidden;
}
if (states.contains(WidgetState.focused)){
return SystemMouseCursors.basic;
}
return SystemMouseCursors.click;
}
@override
String get debugDescription => 'RadioMouseCursor()';
}