Make CupertinoRadio's mouseCursor a WidgetStateProperty (#151910)

https://github.com/flutter/flutter/pull/149681 introduced `mouseCursor `to `CupertinoRadio` as a `MouseCursor` instead of a `WidgetStateProperty` to match Material Radio's `mouseCursor` property for `.adaptive`.

This PR changes `mouseCursor` to be of type `WidgetStateProperty<MouseCursor>` as per review comments in https://github.com/flutter/flutter/pull/151788#discussion_r1680538286.

PR bringing `mouseCursor` into `CupertinoRadio`: https://github.com/flutter/flutter/pull/149681.

Part of https://github.com/flutter/flutter/issues/58192
This commit is contained in:
Victor Sanni 2024-07-19 14:01:10 -07:00 committed by GitHub
parent 14f9b568fc
commit 5ef969a24f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 93 additions and 41 deletions

View File

@ -132,24 +132,20 @@ class CupertinoRadio<T> extends StatefulWidget {
/// 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:
/// Resolves in the following states:
///
/// * [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.
/// Defaults to [defaultMouseCursor].
///
/// 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;
/// either a [MouseCursor] or a [WidgetStateProperty].
final WidgetStateProperty<MouseCursor>? mouseCursor;
/// Set to true if this radio button is allowed to be returned to an
/// indeterminate state by selecting it again when selected.
@ -210,6 +206,18 @@ class CupertinoRadio<T> extends StatefulWidget {
bool get _selected => value == groupValue;
/// The default [mouseCursor] of a [CupertinoRadio].
///
/// If [onChanged] is null, indicating the radio button is disabled,
/// [SystemMouseCursors.basic] is used. Otherwise, [SystemMouseCursors.click]
/// is used on Web, and [SystemMouseCursors.basic] is used on other platforms.
static WidgetStateProperty<MouseCursor> defaultMouseCursor(Function? onChanged) {
final MouseCursor mouseCursor = (onChanged != null && kIsWeb)
? SystemMouseCursors.click
: SystemMouseCursors.basic;
return WidgetStateProperty.all<MouseCursor>(mouseCursor);
}
@override
State<CupertinoRadio<T>> createState() => _CupertinoRadioState<T>();
}
@ -269,15 +277,6 @@ 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.
@ -297,7 +296,7 @@ class _CupertinoRadioState<T> extends State<CupertinoRadio<T>> with TickerProvid
checked: widget._selected,
selected: accessibilitySelected,
child: buildToggleable(
mouseCursor: effectiveMouseCursor,
mouseCursor: widget.mouseCursor ?? CupertinoRadio.defaultMouseCursor(widget.onChanged),
focusNode: widget.focusNode,
autofocus: widget.autofocus,
onFocusChange: onFocusChange,

View File

@ -447,7 +447,11 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg
value: widget.value,
groupValue: widget.groupValue,
onChanged: widget.onChanged,
mouseCursor: widget.mouseCursor,
mouseCursor: widget.mouseCursor == null
? CupertinoRadio.defaultMouseCursor(widget.onChanged)
: WidgetStateProperty.resolveWith((Set<MaterialState> states) {
return WidgetStateProperty.resolveAs<MouseCursor>(widget.mouseCursor!, states);
}),
toggleable: widget.toggleable,
activeColor: widget.activeColor,
focusColor: widget.focusColor,

View File

@ -441,7 +441,7 @@ void main() {
value: 1,
groupValue: 1,
onChanged: (int? i) { },
mouseCursor: SystemMouseCursors.forbidden,
mouseCursor: WidgetStateProperty.all(SystemMouseCursors.forbidden),
),
),
));
@ -463,13 +463,25 @@ void main() {
final FocusNode focusNode = FocusNode(debugLabel: 'Radio');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
MouseCursor getMouseCursor(Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
return SystemMouseCursors.forbidden;
}
if (states.contains(WidgetState.focused)) {
return SystemMouseCursors.basic;
}
return SystemMouseCursors.click;
}
final WidgetStateProperty<MouseCursor> mouseCursor = WidgetStateProperty.resolveWith(getMouseCursor);
await tester.pumpWidget(CupertinoApp(
home: Center(
child: CupertinoRadio<int>(
value: 1,
groupValue: 1,
onChanged: (int? i) { },
mouseCursor: const RadioMouseCursor(),
mouseCursor: mouseCursor,
focusNode: focusNode
),
),
@ -498,13 +510,13 @@ void main() {
);
// Test disabled case.
await tester.pumpWidget(const CupertinoApp(
await tester.pumpWidget(CupertinoApp(
home: Center(
child: CupertinoRadio<int>(
value: 1,
groupValue: 1,
onChanged: null,
mouseCursor: RadioMouseCursor(),
mouseCursor: mouseCursor,
),
),
));
@ -541,21 +553,3 @@ void main() {
);
});
}
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()';
}

View File

@ -1859,6 +1859,46 @@ void main() {
}
});
testWidgets('Radio.adaptive respects Radio.mouseCursor', (WidgetTester tester) async {
Widget buildApp({required TargetPlatform platform, MouseCursor? mouseCursor}) {
return MaterialApp(
theme: ThemeData(platform: platform),
home: Material(
child: Radio<int>.adaptive(
value: 1,
groupValue: 1,
onChanged: (int? i) {},
mouseCursor: mouseCursor,
),
),
);
}
for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.iOS, TargetPlatform.macOS ]) {
await tester.pumpWidget(buildApp(platform: platform));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
// Test default mouse cursor.
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,
);
// Test mouse cursor can be configured.
await tester.pumpWidget(buildApp(platform: platform, mouseCursor: SystemMouseCursors.forbidden));
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
// Test Radio.adaptive can resolve a WidgetStateMouseCursor.
await tester.pumpWidget(buildApp(platform: platform, mouseCursor: const _SelectedGrabMouseCursor()));
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.grab);
await gesture.removePointer();
}
});
testWidgets('Material2 - Radio default overlayColor and fillColor resolves pressed state', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Radio');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
@ -1993,3 +2033,18 @@ void main() {
focusNode.dispose();
});
}
class _SelectedGrabMouseCursor extends WidgetStateMouseCursor {
const _SelectedGrabMouseCursor();
@override
MouseCursor resolve(Set<WidgetState> states) {
if (states.contains(WidgetState.selected)) {
return SystemMouseCursors.grab;
}
return SystemMouseCursors.basic;
}
@override
String get debugDescription => '_SelectedGrabMouseCursor()';
}