Skip to content

Commit f7fb14e

Browse files
authored
Add customizable mouse cursor to DataTable (#123128)
1 parent 25e692c commit f7fb14e

File tree

4 files changed

+329
-17
lines changed

4 files changed

+329
-17
lines changed

packages/flutter/lib/src/material/data_table.dart

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class DataColumn {
4444
this.tooltip,
4545
this.numeric = false,
4646
this.onSort,
47+
this.mouseCursor,
4748
});
4849

4950
/// The column heading.
@@ -85,6 +86,20 @@ class DataColumn {
8586
final DataColumnSortCallback? onSort;
8687

8788
bool get _debugInteractive => onSort != null;
89+
90+
/// The cursor for a mouse pointer when it enters or is hovering over the
91+
/// heading row.
92+
///
93+
/// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
94+
///
95+
/// * [MaterialState.disabled].
96+
///
97+
/// If this is null, then the value of [DataTableThemeData.headingCellCursor]
98+
/// is used. If that's null, then [MaterialStateMouseCursor.clickable] is used.
99+
///
100+
/// See also:
101+
/// * [MaterialStateMouseCursor], which can be used to create a [MouseCursor].
102+
final MaterialStateProperty<MouseCursor?>? mouseCursor;
88103
}
89104

90105
/// Row configuration and cell data for a [DataTable].
@@ -106,6 +121,7 @@ class DataRow {
106121
this.onSelectChanged,
107122
this.onLongPress,
108123
this.color,
124+
this.mouseCursor,
109125
required this.cells,
110126
});
111127

@@ -119,6 +135,7 @@ class DataRow {
119135
this.onSelectChanged,
120136
this.onLongPress,
121137
this.color,
138+
this.mouseCursor,
122139
required this.cells,
123140
}) : key = ValueKey<int?>(index);
124141

@@ -205,6 +222,20 @@ class DataRow {
205222
/// <https://material.io/design/interaction/states.html#anatomy>.
206223
final MaterialStateProperty<Color?>? color;
207224

225+
/// The cursor for a mouse pointer when it enters or is hovering over the
226+
/// data row.
227+
///
228+
/// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
229+
///
230+
/// * [MaterialState.selected].
231+
///
232+
/// If this is null, then the value of [DataTableThemeData.dataRowCursor]
233+
/// is used. If that's null, then [MaterialStateMouseCursor.clickable] is used.
234+
///
235+
/// See also:
236+
/// * [MaterialStateMouseCursor], which can be used to create a [MouseCursor].
237+
final MaterialStateProperty<MouseCursor?>? mouseCursor;
238+
208239
bool get _debugInteractive => onSelectChanged != null || cells.any((DataCell cell) => cell._debugInteractive);
209240
}
210241

@@ -738,6 +769,7 @@ class DataTable extends StatelessWidget {
738769
required ValueChanged<bool?>? onCheckboxChanged,
739770
required MaterialStateProperty<Color?>? overlayColor,
740771
required bool tristate,
772+
MouseCursor? rowMouseCursor,
741773
}) {
742774
final ThemeData themeData = Theme.of(context);
743775
final double effectiveHorizontalMargin = horizontalMargin
@@ -769,6 +801,7 @@ class DataTable extends StatelessWidget {
769801
contents = TableRowInkWell(
770802
onTap: onRowTap,
771803
overlayColor: overlayColor,
804+
mouseCursor: rowMouseCursor,
772805
child: contents,
773806
);
774807
}
@@ -788,6 +821,7 @@ class DataTable extends StatelessWidget {
788821
required bool sorted,
789822
required bool ascending,
790823
required MaterialStateProperty<Color?>? overlayColor,
824+
required MouseCursor? mouseCursor,
791825
}) {
792826
final ThemeData themeData = Theme.of(context);
793827
final DataTableThemeData dataTableTheme = DataTableTheme.of(context);
@@ -838,6 +872,7 @@ class DataTable extends StatelessWidget {
838872
label = InkWell(
839873
onTap: onSort,
840874
overlayColor: overlayColor,
875+
mouseCursor: mouseCursor,
841876
child: label,
842877
);
843878
return label;
@@ -858,6 +893,7 @@ class DataTable extends StatelessWidget {
858893
required GestureTapCancelCallback? onTapCancel,
859894
required MaterialStateProperty<Color?>? overlayColor,
860895
required GestureLongPressCallback? onRowLongPress,
896+
required MouseCursor? mouseCursor,
861897
}) {
862898
final ThemeData themeData = Theme.of(context);
863899
final DataTableThemeData dataTableTheme = DataTableTheme.of(context);
@@ -912,6 +948,7 @@ class DataTable extends StatelessWidget {
912948
onTap: onSelectChanged,
913949
onLongPress: onRowLongPress,
914950
overlayColor: overlayColor,
951+
mouseCursor: mouseCursor,
915952
child: label,
916953
);
917954
}
@@ -1014,12 +1051,17 @@ class DataTable extends StatelessWidget {
10141051
);
10151052
rowIndex = 1;
10161053
for (final DataRow row in rows) {
1054+
final Set<MaterialState> states = <MaterialState>{
1055+
if (row.selected)
1056+
MaterialState.selected,
1057+
};
10171058
tableRows[rowIndex].children[0] = _buildCheckbox(
10181059
context: context,
10191060
checked: row.selected,
10201061
onRowTap: row.onSelectChanged == null ? null : () => row.onSelectChanged?.call(!row.selected),
10211062
onCheckboxChanged: row.onSelectChanged,
10221063
overlayColor: row.color ?? effectiveDataRowColor,
1064+
rowMouseCursor: row.mouseCursor?.resolve(states) ?? dataTableTheme.dataRowCursor?.resolve(states),
10231065
tristate: false,
10241066
);
10251067
rowIndex += 1;
@@ -1057,6 +1099,10 @@ class DataTable extends StatelessWidget {
10571099
} else {
10581100
tableColumns[displayColumnIndex] = const IntrinsicColumnWidth();
10591101
}
1102+
final Set<MaterialState> headerStates = <MaterialState>{
1103+
if (column.onSort == null)
1104+
MaterialState.disabled,
1105+
};
10601106
tableRows[0].children[displayColumnIndex] = _buildHeadingCell(
10611107
context: context,
10621108
padding: padding,
@@ -1067,9 +1113,14 @@ class DataTable extends StatelessWidget {
10671113
sorted: dataColumnIndex == sortColumnIndex,
10681114
ascending: sortAscending,
10691115
overlayColor: effectiveHeadingRowColor,
1116+
mouseCursor: column.mouseCursor?.resolve(headerStates) ?? dataTableTheme.headingCellCursor?.resolve(headerStates),
10701117
);
10711118
rowIndex = 1;
10721119
for (final DataRow row in rows) {
1120+
final Set<MaterialState> states = <MaterialState>{
1121+
if (row.selected)
1122+
MaterialState.selected,
1123+
};
10731124
final DataCell cell = row.cells[dataColumnIndex];
10741125
tableRows[rowIndex].children[displayColumnIndex] = _buildDataCell(
10751126
context: context,
@@ -1086,6 +1137,7 @@ class DataTable extends StatelessWidget {
10861137
onSelectChanged: row.onSelectChanged == null ? null : () => row.onSelectChanged?.call(!row.selected),
10871138
overlayColor: row.color ?? effectiveDataRowColor,
10881139
onRowLongPress: row.onLongPress,
1140+
mouseCursor: row.mouseCursor?.resolve(states) ?? dataTableTheme.dataRowCursor?.resolve(states),
10891141
);
10901142
rowIndex += 1;
10911143
}
@@ -1138,6 +1190,7 @@ class TableRowInkWell extends InkResponse {
11381190
super.onLongPress,
11391191
super.onHighlightChanged,
11401192
super.overlayColor,
1193+
super.mouseCursor,
11411194
}) : super(
11421195
containedInkWell: true,
11431196
highlightShape: BoxShape.rectangle,

packages/flutter/lib/src/material/data_table_theme.dart

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ class DataTableThemeData with Diagnosticable {
5555
this.columnSpacing,
5656
this.dividerThickness,
5757
this.checkboxHorizontalMargin,
58+
this.headingCellCursor,
59+
this.dataRowCursor,
5860
}) : assert(dataRowMinHeight == null || dataRowMaxHeight == null || dataRowMaxHeight >= dataRowMinHeight),
5961
assert(dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null),
6062
'dataRowHeight ($dataRowHeight) must not be set if dataRowMinHeight ($dataRowMinHeight) or dataRowMaxHeight ($dataRowMaxHeight) are set.'),
@@ -106,6 +108,12 @@ class DataTableThemeData with Diagnosticable {
106108
/// {@macro flutter.material.dataTable.checkboxHorizontalMargin}
107109
final double? checkboxHorizontalMargin;
108110

111+
/// If specified, overrides the default value of [DataColumn.mouseCursor].
112+
final MaterialStateProperty<MouseCursor?>? headingCellCursor;
113+
114+
/// If specified, overrides the default value of [DataRow.mouseCursor].
115+
final MaterialStateProperty<MouseCursor?>? dataRowCursor;
116+
109117
/// Creates a copy of this object but with the given fields replaced with the
110118
/// new values.
111119
DataTableThemeData copyWith({
@@ -126,6 +134,8 @@ class DataTableThemeData with Diagnosticable {
126134
double? columnSpacing,
127135
double? dividerThickness,
128136
double? checkboxHorizontalMargin,
137+
MaterialStateProperty<MouseCursor?>? headingCellCursor,
138+
MaterialStateProperty<MouseCursor?>? dataRowCursor,
129139
}) {
130140
return DataTableThemeData(
131141
decoration: decoration ?? this.decoration,
@@ -141,6 +151,8 @@ class DataTableThemeData with Diagnosticable {
141151
columnSpacing: columnSpacing ?? this.columnSpacing,
142152
dividerThickness: dividerThickness ?? this.dividerThickness,
143153
checkboxHorizontalMargin: checkboxHorizontalMargin ?? this.checkboxHorizontalMargin,
154+
headingCellCursor: headingCellCursor ?? this.headingCellCursor,
155+
dataRowCursor: dataRowCursor ?? this.dataRowCursor,
144156
);
145157
}
146158

@@ -166,6 +178,8 @@ class DataTableThemeData with Diagnosticable {
166178
columnSpacing: lerpDouble(a.columnSpacing, b.columnSpacing, t),
167179
dividerThickness: lerpDouble(a.dividerThickness, b.dividerThickness, t),
168180
checkboxHorizontalMargin: lerpDouble(a.checkboxHorizontalMargin, b.checkboxHorizontalMargin, t),
181+
headingCellCursor: t < 0.5 ? a.headingCellCursor : b.headingCellCursor,
182+
dataRowCursor: t < 0.5 ? a.dataRowCursor : b.dataRowCursor,
169183
);
170184
}
171185

@@ -183,6 +197,8 @@ class DataTableThemeData with Diagnosticable {
183197
columnSpacing,
184198
dividerThickness,
185199
checkboxHorizontalMargin,
200+
headingCellCursor,
201+
dataRowCursor,
186202
);
187203

188204
@override
@@ -205,7 +221,9 @@ class DataTableThemeData with Diagnosticable {
205221
&& other.horizontalMargin == horizontalMargin
206222
&& other.columnSpacing == columnSpacing
207223
&& other.dividerThickness == dividerThickness
208-
&& other.checkboxHorizontalMargin == checkboxHorizontalMargin;
224+
&& other.checkboxHorizontalMargin == checkboxHorizontalMargin
225+
&& other.headingCellCursor == headingCellCursor
226+
&& other.dataRowCursor == dataRowCursor;
209227
}
210228

211229
@override
@@ -223,6 +241,8 @@ class DataTableThemeData with Diagnosticable {
223241
properties.add(DoubleProperty('columnSpacing', columnSpacing, defaultValue: null));
224242
properties.add(DoubleProperty('dividerThickness', dividerThickness, defaultValue: null));
225243
properties.add(DoubleProperty('checkboxHorizontalMargin', checkboxHorizontalMargin, defaultValue: null));
244+
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>?>('headingCellCursor', headingCellCursor, defaultValue: null));
245+
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>?>('dataRowCursor', dataRowCursor, defaultValue: null));
226246
}
227247
}
228248

0 commit comments

Comments
 (0)