Add mouseCursor
property to CupertinoCheckbox
(#151788)
Part of https://github.com/flutter/flutter/issues/58192
This commit is contained in:
parent
896e322fd6
commit
d0f2a6887e
@ -96,6 +96,7 @@ class CupertinoCheckbox extends StatefulWidget {
|
||||
required this.value,
|
||||
this.tristate = false,
|
||||
required this.onChanged,
|
||||
this.mouseCursor,
|
||||
this.activeColor,
|
||||
@Deprecated(
|
||||
'Use fillColor instead. '
|
||||
@ -150,6 +151,30 @@ class CupertinoCheckbox extends StatefulWidget {
|
||||
/// ```
|
||||
final ValueChanged<bool?>? onChanged;
|
||||
|
||||
/// The cursor for a mouse pointer when it enters or is hovering over the
|
||||
/// widget.
|
||||
///
|
||||
/// If [mouseCursor] is a [WidgetStateMouseCursor],
|
||||
/// [WidgetStateProperty.resolve] is used for the following [WidgetState]s:
|
||||
///
|
||||
/// * [WidgetState.selected].
|
||||
/// * [WidgetState.focused].
|
||||
/// * [WidgetState.disabled].
|
||||
///
|
||||
/// When [value] is null and [tristate] is true, [WidgetState.selected] is
|
||||
/// included as a state.
|
||||
///
|
||||
/// If null, then [SystemMouseCursors.basic] is used when this checkbox is
|
||||
/// disabled. When the checkbox 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].
|
||||
final MouseCursor? mouseCursor;
|
||||
|
||||
/// The color to use when this checkbox is checked.
|
||||
///
|
||||
/// If [fillColor] returns a non-null color in the [WidgetState.selected]
|
||||
@ -386,11 +411,21 @@ class _CupertinoCheckboxState extends State<CupertinoCheckbox> with TickerProvid
|
||||
.withSaturation(kCupertinoFocusColorSaturation)
|
||||
.toColor();
|
||||
|
||||
final WidgetStateProperty<MouseCursor> effectiveMouseCursor =
|
||||
WidgetStateProperty.resolveWith<MouseCursor>((Set<WidgetState> states) {
|
||||
return WidgetStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, states)
|
||||
?? (kIsWeb && !states.contains(WidgetState.disabled)
|
||||
? SystemMouseCursors.click
|
||||
: SystemMouseCursors.basic
|
||||
);
|
||||
});
|
||||
|
||||
return Semantics(
|
||||
label: widget.semanticLabel,
|
||||
checked: widget.value ?? false,
|
||||
mixed: widget.tristate ? widget.value == null : null,
|
||||
child: buildToggleable(
|
||||
mouseCursor: effectiveMouseCursor,
|
||||
focusNode: widget.focusNode,
|
||||
autofocus: widget.autofocus,
|
||||
size: const Size.square(kMinInteractiveDimensionCupertino),
|
||||
|
@ -115,7 +115,7 @@ class Checkbox extends StatefulWidget {
|
||||
/// design [Checkbox].
|
||||
///
|
||||
/// If a [CupertinoCheckbox] is created, the following parameters are ignored:
|
||||
/// [mouseCursor], [fillColor], [hoverColor], [overlayColor], [splashRadius],
|
||||
/// [fillColor], [hoverColor], [overlayColor], [splashRadius],
|
||||
/// [materialTapTargetSize], [visualDensity], [isError]. However, [shape] and
|
||||
/// [side] will still affect the [CupertinoCheckbox] and should be handled if
|
||||
/// native fidelity is important.
|
||||
@ -184,11 +184,10 @@ class Checkbox extends StatefulWidget {
|
||||
/// The cursor for a mouse pointer when it enters or is hovering over the
|
||||
/// widget.
|
||||
///
|
||||
/// If [mouseCursor] is a [WidgetStateProperty<MouseCursor>],
|
||||
/// If [mouseCursor] is a [WidgetStateMouseCursor],
|
||||
/// [WidgetStateProperty.resolve] is used for the following [WidgetState]s:
|
||||
///
|
||||
/// * [WidgetState.selected].
|
||||
/// * [WidgetState.hovered].
|
||||
/// * [WidgetState.focused].
|
||||
/// * [WidgetState.disabled].
|
||||
/// {@endtemplate}
|
||||
@ -202,8 +201,8 @@ class Checkbox extends StatefulWidget {
|
||||
/// See also:
|
||||
///
|
||||
/// * [WidgetStateMouseCursor], a [MouseCursor] that implements
|
||||
/// `WidgetStateProperty` which is used in APIs that need to accept
|
||||
/// either a [MouseCursor] or a [WidgetStateProperty<MouseCursor>].
|
||||
/// [WidgetStateProperty] which is used in APIs that need to accept
|
||||
/// either a [MouseCursor] or a [WidgetStateProperty].
|
||||
final MouseCursor? mouseCursor;
|
||||
|
||||
/// The color to use when this checkbox is checked.
|
||||
@ -496,6 +495,7 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
|
||||
value: value,
|
||||
tristate: tristate,
|
||||
onChanged: onChanged,
|
||||
mouseCursor: widget.mouseCursor,
|
||||
activeColor: widget.activeColor,
|
||||
checkColor: widget.checkColor,
|
||||
focusColor: widget.focusColor,
|
||||
|
@ -7,10 +7,10 @@
|
||||
// machines.
|
||||
@Tags(<String>['reduced-test-set'])
|
||||
library;
|
||||
import 'dart:ui';
|
||||
|
||||
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';
|
||||
@ -474,6 +474,80 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Checkbox configures mouse cursor', (WidgetTester tester) async {
|
||||
Widget buildApp({ MouseCursor? mouseCursor, bool enabled = true, bool value = true }) {
|
||||
return CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoCheckbox(
|
||||
value: value,
|
||||
onChanged: enabled ? (bool? value) {} : null,
|
||||
mouseCursor: mouseCursor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
await tester.pumpWidget(buildApp(value: false));
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.addPointer(location: tester.getCenter(find.byType(CupertinoCheckbox)));
|
||||
await tester.pump();
|
||||
await gesture.moveTo(tester.getCenter(find.byType(CupertinoCheckbox)));
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
|
||||
);
|
||||
|
||||
// Test disabled checkbox.
|
||||
await tester.pumpWidget(buildApp(enabled: false, value: false));
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
|
||||
|
||||
// Test mouse cursor can be configured.
|
||||
await tester.pumpWidget(buildApp(mouseCursor: SystemMouseCursors.grab));
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.grab);
|
||||
});
|
||||
|
||||
testWidgets('Mouse cursor resolves in selected/focused/disabled states', (WidgetTester tester) async {
|
||||
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
||||
final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox');
|
||||
addTearDown(focusNode.dispose);
|
||||
|
||||
Widget buildCheckbox({ required bool value, required bool enabled }) {
|
||||
return CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoCheckbox(
|
||||
value: value,
|
||||
onChanged: enabled ? (bool? value) => true : null,
|
||||
mouseCursor: const _CheckboxMouseCursor(),
|
||||
focusNode: focusNode
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Test unselected case.
|
||||
await tester.pumpWidget(buildCheckbox(value: false, enabled: true));
|
||||
final TestGesture gesture1 = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
|
||||
addTearDown(gesture1.removePointer);
|
||||
await gesture1.addPointer(location: tester.getCenter(find.byType(CupertinoCheckbox)));
|
||||
await tester.pump();
|
||||
await gesture1.moveTo(tester.getCenter(find.byType(CupertinoCheckbox)));
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
|
||||
|
||||
// Test selected case.
|
||||
await tester.pumpWidget(buildCheckbox(value: true, enabled: true));
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
|
||||
|
||||
// Test focused case.
|
||||
await tester.pumpWidget(buildCheckbox(value: true, enabled: true));
|
||||
focusNode.requestFocus();
|
||||
await tester.pump();
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.grab);
|
||||
|
||||
// Test disabled case.
|
||||
await tester.pumpWidget(buildCheckbox(value: true, enabled: false));
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
|
||||
});
|
||||
|
||||
testWidgets('Checkbox default colors, and size in light mode', (WidgetTester tester) async {
|
||||
Widget buildCheckbox({bool value = true}) {
|
||||
return CupertinoApp(
|
||||
@ -883,3 +957,21 @@ void main() {
|
||||
await tester.pump();
|
||||
});
|
||||
}
|
||||
|
||||
class _CheckboxMouseCursor extends WidgetStateMouseCursor {
|
||||
const _CheckboxMouseCursor();
|
||||
|
||||
@override
|
||||
MouseCursor resolve(Set<WidgetState> states) {
|
||||
return const WidgetStateProperty<MouseCursor>.fromMap(
|
||||
<WidgetStatesConstraint, MouseCursor>{
|
||||
WidgetState.disabled: SystemMouseCursors.forbidden,
|
||||
WidgetState.focused: SystemMouseCursors.grab,
|
||||
WidgetState.selected: SystemMouseCursors.click,
|
||||
WidgetState.any: SystemMouseCursors.basic,
|
||||
},
|
||||
).resolve(states);
|
||||
}
|
||||
@override
|
||||
String get debugDescription => '_CheckboxMouseCursor()';
|
||||
}
|
||||
|
@ -823,7 +823,7 @@ void main() {
|
||||
value: 1,
|
||||
groupValue: 1,
|
||||
onChanged: (int? i) { },
|
||||
mouseCursor: const RadioMouseCursor(),
|
||||
mouseCursor: const _RadioMouseCursor(),
|
||||
focusNode: focusNode
|
||||
),
|
||||
),
|
||||
@ -858,7 +858,7 @@ void main() {
|
||||
value: 1,
|
||||
groupValue: 1,
|
||||
onChanged: null,
|
||||
mouseCursor: RadioMouseCursor(),
|
||||
mouseCursor: _RadioMouseCursor(),
|
||||
),
|
||||
),
|
||||
));
|
||||
@ -896,8 +896,8 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
class RadioMouseCursor extends WidgetStateMouseCursor {
|
||||
const RadioMouseCursor();
|
||||
class _RadioMouseCursor extends WidgetStateMouseCursor {
|
||||
const _RadioMouseCursor();
|
||||
|
||||
@override
|
||||
MouseCursor resolve(Set<WidgetState> states) {
|
||||
@ -911,5 +911,5 @@ class RadioMouseCursor extends WidgetStateMouseCursor {
|
||||
}
|
||||
|
||||
@override
|
||||
String get debugDescription => 'RadioMouseCursor()';
|
||||
String get debugDescription => '_RadioMouseCursor()';
|
||||
}
|
||||
|
@ -2235,6 +2235,39 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('Checkbox.adaptive respects Checkbox.mouseCursor on iOS/macOS', (WidgetTester tester) async {
|
||||
Widget buildApp({ MouseCursor? mouseCursor }) {
|
||||
return MaterialApp(
|
||||
home: Material(
|
||||
child: Checkbox.adaptive(
|
||||
value: true,
|
||||
onChanged: (bool? newValue) { },
|
||||
mouseCursor: mouseCursor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
await tester.pumpWidget(buildApp());
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
|
||||
await gesture.addPointer(location: tester.getCenter(find.byType(CupertinoCheckbox)));
|
||||
await tester.pump();
|
||||
await gesture.moveTo(tester.getCenter(find.byType(CupertinoCheckbox)));
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
|
||||
);
|
||||
|
||||
// Test mouse cursor can be configured.
|
||||
await tester.pumpWidget(buildApp(mouseCursor: SystemMouseCursors.click));
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
|
||||
|
||||
// Test Checkbox.adaptive can resolve a WidgetStateMouseCursor.
|
||||
await tester.pumpWidget(buildApp(mouseCursor: const _SelectedGrabMouseCursor()));
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.grab);
|
||||
|
||||
await gesture.removePointer();
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS, TargetPlatform.macOS}));
|
||||
|
||||
testWidgets('Material2 - Checkbox respects fillColor when it is unchecked', (WidgetTester tester) async {
|
||||
final ThemeData theme = ThemeData(useMaterial3: false);
|
||||
const Color activeBackgroundColor = Color(0xff123456);
|
||||
|
Loading…
x
Reference in New Issue
Block a user