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,
|
required this.value,
|
||||||
this.tristate = false,
|
this.tristate = false,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
|
this.mouseCursor,
|
||||||
this.activeColor,
|
this.activeColor,
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
'Use fillColor instead. '
|
'Use fillColor instead. '
|
||||||
@ -150,6 +151,30 @@ class CupertinoCheckbox extends StatefulWidget {
|
|||||||
/// ```
|
/// ```
|
||||||
final ValueChanged<bool?>? onChanged;
|
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.
|
/// The color to use when this checkbox is checked.
|
||||||
///
|
///
|
||||||
/// If [fillColor] returns a non-null color in the [WidgetState.selected]
|
/// If [fillColor] returns a non-null color in the [WidgetState.selected]
|
||||||
@ -386,11 +411,21 @@ class _CupertinoCheckboxState extends State<CupertinoCheckbox> with TickerProvid
|
|||||||
.withSaturation(kCupertinoFocusColorSaturation)
|
.withSaturation(kCupertinoFocusColorSaturation)
|
||||||
.toColor();
|
.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(
|
return Semantics(
|
||||||
label: widget.semanticLabel,
|
label: widget.semanticLabel,
|
||||||
checked: widget.value ?? false,
|
checked: widget.value ?? false,
|
||||||
mixed: widget.tristate ? widget.value == null : null,
|
mixed: widget.tristate ? widget.value == null : null,
|
||||||
child: buildToggleable(
|
child: buildToggleable(
|
||||||
|
mouseCursor: effectiveMouseCursor,
|
||||||
focusNode: widget.focusNode,
|
focusNode: widget.focusNode,
|
||||||
autofocus: widget.autofocus,
|
autofocus: widget.autofocus,
|
||||||
size: const Size.square(kMinInteractiveDimensionCupertino),
|
size: const Size.square(kMinInteractiveDimensionCupertino),
|
||||||
|
@ -115,7 +115,7 @@ class Checkbox extends StatefulWidget {
|
|||||||
/// design [Checkbox].
|
/// design [Checkbox].
|
||||||
///
|
///
|
||||||
/// If a [CupertinoCheckbox] is created, the following parameters are ignored:
|
/// 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
|
/// [materialTapTargetSize], [visualDensity], [isError]. However, [shape] and
|
||||||
/// [side] will still affect the [CupertinoCheckbox] and should be handled if
|
/// [side] will still affect the [CupertinoCheckbox] and should be handled if
|
||||||
/// native fidelity is important.
|
/// 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
|
/// The cursor for a mouse pointer when it enters or is hovering over the
|
||||||
/// widget.
|
/// widget.
|
||||||
///
|
///
|
||||||
/// If [mouseCursor] is a [WidgetStateProperty<MouseCursor>],
|
/// If [mouseCursor] is a [WidgetStateMouseCursor],
|
||||||
/// [WidgetStateProperty.resolve] is used for the following [WidgetState]s:
|
/// [WidgetStateProperty.resolve] is used for the following [WidgetState]s:
|
||||||
///
|
///
|
||||||
/// * [WidgetState.selected].
|
/// * [WidgetState.selected].
|
||||||
/// * [WidgetState.hovered].
|
|
||||||
/// * [WidgetState.focused].
|
/// * [WidgetState.focused].
|
||||||
/// * [WidgetState.disabled].
|
/// * [WidgetState.disabled].
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
@ -202,8 +201,8 @@ class Checkbox extends StatefulWidget {
|
|||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [WidgetStateMouseCursor], a [MouseCursor] that implements
|
/// * [WidgetStateMouseCursor], a [MouseCursor] that implements
|
||||||
/// `WidgetStateProperty` which is used in APIs that need to accept
|
/// [WidgetStateProperty] which is used in APIs that need to accept
|
||||||
/// either a [MouseCursor] or a [WidgetStateProperty<MouseCursor>].
|
/// either a [MouseCursor] or a [WidgetStateProperty].
|
||||||
final MouseCursor? mouseCursor;
|
final MouseCursor? mouseCursor;
|
||||||
|
|
||||||
/// The color to use when this checkbox is checked.
|
/// The color to use when this checkbox is checked.
|
||||||
@ -496,6 +495,7 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
|
|||||||
value: value,
|
value: value,
|
||||||
tristate: tristate,
|
tristate: tristate,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
|
mouseCursor: widget.mouseCursor,
|
||||||
activeColor: widget.activeColor,
|
activeColor: widget.activeColor,
|
||||||
checkColor: widget.checkColor,
|
checkColor: widget.checkColor,
|
||||||
focusColor: widget.focusColor,
|
focusColor: widget.focusColor,
|
||||||
|
@ -7,10 +7,10 @@
|
|||||||
// machines.
|
// machines.
|
||||||
@Tags(<String>['reduced-test-set'])
|
@Tags(<String>['reduced-test-set'])
|
||||||
library;
|
library;
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.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 {
|
testWidgets('Checkbox default colors, and size in light mode', (WidgetTester tester) async {
|
||||||
Widget buildCheckbox({bool value = true}) {
|
Widget buildCheckbox({bool value = true}) {
|
||||||
return CupertinoApp(
|
return CupertinoApp(
|
||||||
@ -883,3 +957,21 @@ void main() {
|
|||||||
await tester.pump();
|
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,
|
value: 1,
|
||||||
groupValue: 1,
|
groupValue: 1,
|
||||||
onChanged: (int? i) { },
|
onChanged: (int? i) { },
|
||||||
mouseCursor: const RadioMouseCursor(),
|
mouseCursor: const _RadioMouseCursor(),
|
||||||
focusNode: focusNode
|
focusNode: focusNode
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -858,7 +858,7 @@ void main() {
|
|||||||
value: 1,
|
value: 1,
|
||||||
groupValue: 1,
|
groupValue: 1,
|
||||||
onChanged: null,
|
onChanged: null,
|
||||||
mouseCursor: RadioMouseCursor(),
|
mouseCursor: _RadioMouseCursor(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
@ -896,8 +896,8 @@ void main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class RadioMouseCursor extends WidgetStateMouseCursor {
|
class _RadioMouseCursor extends WidgetStateMouseCursor {
|
||||||
const RadioMouseCursor();
|
const _RadioMouseCursor();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MouseCursor resolve(Set<WidgetState> states) {
|
MouseCursor resolve(Set<WidgetState> states) {
|
||||||
@ -911,5 +911,5 @@ class RadioMouseCursor extends WidgetStateMouseCursor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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 {
|
testWidgets('Material2 - Checkbox respects fillColor when it is unchecked', (WidgetTester tester) async {
|
||||||
final ThemeData theme = ThemeData(useMaterial3: false);
|
final ThemeData theme = ThemeData(useMaterial3: false);
|
||||||
const Color activeBackgroundColor = Color(0xff123456);
|
const Color activeBackgroundColor = Color(0xff123456);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user