Unverified Commit fd6547e1 authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

ListTile: add themeable mouse cursor (#96740)

parent 3a98d8f9
......@@ -99,6 +99,7 @@ class ListTileThemeData with Diagnosticable {
this.minVerticalPadding,
this.minLeadingWidth,
this.enableFeedback,
this.mouseCursor,
});
/// Overrides the default value of [ListTile.dense].
......@@ -140,6 +141,9 @@ class ListTileThemeData with Diagnosticable {
/// Overrides the default value of [ListTile.enableFeedback].
final bool? enableFeedback;
/// If specified, overrides the default value of [ListTile.mouseCursor].
final MaterialStateProperty<MouseCursor?>? mouseCursor;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
ListTileThemeData copyWith({
......@@ -156,6 +160,7 @@ class ListTileThemeData with Diagnosticable {
double? minVerticalPadding,
double? minLeadingWidth,
bool? enableFeedback,
MaterialStateProperty<MouseCursor?>? mouseCursor,
}) {
return ListTileThemeData(
dense: dense ?? this.dense,
......@@ -171,6 +176,7 @@ class ListTileThemeData with Diagnosticable {
minVerticalPadding: minVerticalPadding ?? this.minVerticalPadding,
minLeadingWidth: minLeadingWidth ?? this.minLeadingWidth,
enableFeedback: enableFeedback ?? this.enableFeedback,
mouseCursor: mouseCursor ?? this.mouseCursor,
);
}
......@@ -193,6 +199,7 @@ class ListTileThemeData with Diagnosticable {
minVerticalPadding: lerpDouble(a?.minVerticalPadding, b?.minVerticalPadding, t),
minLeadingWidth: lerpDouble(a?.minLeadingWidth, b?.minLeadingWidth, t),
enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
);
}
......@@ -212,6 +219,7 @@ class ListTileThemeData with Diagnosticable {
minVerticalPadding,
minLeadingWidth,
enableFeedback,
mouseCursor,
);
}
......@@ -234,7 +242,8 @@ class ListTileThemeData with Diagnosticable {
&& other.horizontalTitleGap == horizontalTitleGap
&& other.minVerticalPadding == minVerticalPadding
&& other.minLeadingWidth == minLeadingWidth
&& other.enableFeedback == enableFeedback;
&& other.enableFeedback == enableFeedback
&& other.mouseCursor == mouseCursor;
}
@override
......@@ -253,6 +262,7 @@ class ListTileThemeData with Diagnosticable {
properties.add(DoubleProperty('minVerticalPadding', minVerticalPadding, defaultValue: null));
properties.add(DoubleProperty('minLeadingWidth', minLeadingWidth, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
}
}
......@@ -283,6 +293,7 @@ class ListTileTheme extends InheritedTheme {
Color? tileColor,
Color? selectedTileColor,
bool? enableFeedback,
MaterialStateProperty<MouseCursor?>? mouseCursor,
double? horizontalTitleGap,
double? minVerticalPadding,
double? minLeadingWidth,
......@@ -297,6 +308,7 @@ class ListTileTheme extends InheritedTheme {
tileColor ??
selectedTileColor ??
enableFeedback ??
mouseCursor ??
horizontalTitleGap ??
minVerticalPadding ??
minLeadingWidth) == null),
......@@ -311,6 +323,7 @@ class ListTileTheme extends InheritedTheme {
_tileColor = tileColor,
_selectedTileColor = selectedTileColor,
_enableFeedback = enableFeedback,
_mouseCursor = mouseCursor,
_horizontalTitleGap = horizontalTitleGap,
_minVerticalPadding = minVerticalPadding,
_minLeadingWidth = minLeadingWidth,
......@@ -330,6 +343,7 @@ class ListTileTheme extends InheritedTheme {
final double? _minVerticalPadding;
final double? _minLeadingWidth;
final bool? _enableFeedback;
final MaterialStateProperty<MouseCursor?>? _mouseCursor;
/// The configuration of this theme.
ListTileThemeData get data {
......@@ -344,6 +358,7 @@ class ListTileTheme extends InheritedTheme {
tileColor: _tileColor,
selectedTileColor: _selectedTileColor,
enableFeedback: _enableFeedback,
mouseCursor: _mouseCursor,
horizontalTitleGap: _horizontalTitleGap,
minVerticalPadding: _minVerticalPadding,
minLeadingWidth: _minLeadingWidth,
......@@ -951,6 +966,7 @@ class ListTile extends StatelessWidget {
/// Inoperative if [enabled] is false.
final GestureLongPressCallback? onLongPress;
/// {@template flutter.material.ListTile.mouseCursor}
/// The cursor for a mouse pointer when it enters or is hovering over the
/// widget.
///
......@@ -959,8 +975,15 @@ class ListTile extends StatelessWidget {
///
/// * [MaterialState.selected].
/// * [MaterialState.disabled].
/// {@endtemplate}
///
/// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
/// If null, then the value of [ListTileThemeData.mouseCursor] is used. If
/// that is also null, then [MaterialStateMouseCursor.clickable] is used.
///
/// See also:
///
/// * [MaterialStateMouseCursor], which can be used to create a [MouseCursor]
/// that is also a [MaterialStateProperty<MouseCursor>].
final MouseCursor? mouseCursor;
/// If this tile is also [enabled] then icons and text are rendered with the same color.
......@@ -1203,19 +1226,20 @@ class ListTile extends StatelessWidget {
?? tileTheme.contentPadding?.resolve(textDirection)
?? defaultContentPadding;
final MouseCursor resolvedMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
mouseCursor ?? MaterialStateMouseCursor.clickable,
<MaterialState>{
if (!enabled || (onTap == null && onLongPress == null)) MaterialState.disabled,
if (selected) MaterialState.selected,
},
);
final Set<MaterialState> states = <MaterialState>{
if (!enabled || (onTap == null && onLongPress == null)) MaterialState.disabled,
if (selected) MaterialState.selected,
};
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(mouseCursor, states)
?? tileTheme.mouseCursor?.resolve(states)
?? MaterialStateMouseCursor.clickable.resolve(states);
return InkWell(
customBorder: shape ?? tileTheme.shape,
onTap: enabled ? onTap : null,
onLongPress: enabled ? onLongPress : null,
mouseCursor: resolvedMouseCursor,
mouseCursor: effectiveMouseCursor,
canRequestFocus: enabled,
focusNode: focusNode,
focusColor: focusColor,
......
......@@ -71,6 +71,7 @@ void main() {
expect(themeData.minVerticalPadding, null);
expect(themeData.minLeadingWidth, null);
expect(themeData.enableFeedback, null);
expect(themeData.mouseCursor, null);
});
testWidgets('Default ListTileThemeData debugFillProperties', (WidgetTester tester) async {
......@@ -101,6 +102,7 @@ void main() {
minVerticalPadding: 300,
minLeadingWidth: 400,
enableFeedback: true,
mouseCursor: MaterialStateMouseCursor.clickable,
).debugFillProperties(builder);
final List<String> description = builder.properties
......@@ -121,7 +123,8 @@ void main() {
'horizontalTitleGap: 200.0',
'minVerticalPadding: 300.0',
'minLeadingWidth: 400.0',
'enableFeedback: true'
'enableFeedback: true',
'mouseCursor: MaterialStateMouseCursor(clickable)',
]);
});
......@@ -145,6 +148,7 @@ void main() {
minVerticalPadding: 300,
minLeadingWidth: 400,
enableFeedback: true,
mouseCursor: MaterialStateMouseCursor.clickable,
child: Center(
child: Builder(
builder: (BuildContext context) {
......@@ -171,6 +175,7 @@ void main() {
expect(theme.minVerticalPadding, 300);
expect(theme.minLeadingWidth, 400);
expect(theme.enableFeedback, true);
expect(theme.mouseCursor, MaterialStateMouseCursor.clickable);
});
testWidgets('ListTile geometry (LTR)', (WidgetTester tester) async {
......@@ -424,6 +429,13 @@ void main() {
selectedColor: selectedColor,
iconColor: iconColor,
textColor: textColor,
mouseCursor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return SystemMouseCursors.forbidden;
}
return SystemMouseCursors.click;
}),
),
child: Builder(
builder: (BuildContext context) {
......@@ -499,6 +511,16 @@ void main() {
// A selected ListTile's InkWell gets the ListTileTheme's shape
await tester.pumpWidget(buildFrame(selected: true, shape: roundedShape));
expect(inkWellBorder(), roundedShape);
// Cursor updates when hovering disabled ListTile
await tester.pumpWidget(buildFrame(enabled: false));
final Offset listTile = tester.getCenter(find.byKey(titleKey));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(listTile);
await tester.pumpAndSettle();
expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
});
testWidgets('ListTile semantics', (WidgetTester tester) async {
......@@ -2071,6 +2093,34 @@ void main() {
expect(feedback.clickSoundCount, 1);
expect(feedback.hapticCount, 0);
});
testWidgets('ListTile.mouseCursor overrides ListTileTheme.mouseCursor', (WidgetTester tester) async {
final Key tileKey = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListTileTheme(
data: const ListTileThemeData(mouseCursor: MaterialStateMouseCursor.clickable),
child: ListTile(
key: tileKey,
mouseCursor: MaterialStateMouseCursor.textable,
title: const Text('Title'),
onTap: () {},
),
),
),
),
);
final Offset listTile = tester.getCenter(find.byKey(tileKey));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(listTile);
await tester.pumpAndSettle();
expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
});
});
testWidgets('ListTile horizontalTitleGap = 0.0', (WidgetTester tester) async {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment