Skip to content

Commit

Permalink
Add customizable mouse cursor to DataTable (#123128)
Browse files Browse the repository at this point in the history
  • Loading branch information
TahaTesser committed Mar 24, 2023
1 parent 25e692c commit f7fb14e
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 17 deletions.
53 changes: 53 additions & 0 deletions packages/flutter/lib/src/material/data_table.dart
Expand Up @@ -44,6 +44,7 @@ class DataColumn {
this.tooltip,
this.numeric = false,
this.onSort,
this.mouseCursor,
});

/// The column heading.
Expand Down Expand Up @@ -85,6 +86,20 @@ class DataColumn {
final DataColumnSortCallback? onSort;

bool get _debugInteractive => onSort != null;

/// The cursor for a mouse pointer when it enters or is hovering over the
/// heading row.
///
/// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
///
/// * [MaterialState.disabled].
///
/// If this is null, then the value of [DataTableThemeData.headingCellCursor]
/// is used. If that's null, then [MaterialStateMouseCursor.clickable] is used.
///
/// See also:
/// * [MaterialStateMouseCursor], which can be used to create a [MouseCursor].
final MaterialStateProperty<MouseCursor?>? mouseCursor;
}

/// Row configuration and cell data for a [DataTable].
Expand All @@ -106,6 +121,7 @@ class DataRow {
this.onSelectChanged,
this.onLongPress,
this.color,
this.mouseCursor,
required this.cells,
});

Expand All @@ -119,6 +135,7 @@ class DataRow {
this.onSelectChanged,
this.onLongPress,
this.color,
this.mouseCursor,
required this.cells,
}) : key = ValueKey<int?>(index);

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

/// The cursor for a mouse pointer when it enters or is hovering over the
/// data row.
///
/// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
///
/// * [MaterialState.selected].
///
/// If this is null, then the value of [DataTableThemeData.dataRowCursor]
/// is used. If that's null, then [MaterialStateMouseCursor.clickable] is used.
///
/// See also:
/// * [MaterialStateMouseCursor], which can be used to create a [MouseCursor].
final MaterialStateProperty<MouseCursor?>? mouseCursor;

bool get _debugInteractive => onSelectChanged != null || cells.any((DataCell cell) => cell._debugInteractive);
}

Expand Down Expand Up @@ -738,6 +769,7 @@ class DataTable extends StatelessWidget {
required ValueChanged<bool?>? onCheckboxChanged,
required MaterialStateProperty<Color?>? overlayColor,
required bool tristate,
MouseCursor? rowMouseCursor,
}) {
final ThemeData themeData = Theme.of(context);
final double effectiveHorizontalMargin = horizontalMargin
Expand Down Expand Up @@ -769,6 +801,7 @@ class DataTable extends StatelessWidget {
contents = TableRowInkWell(
onTap: onRowTap,
overlayColor: overlayColor,
mouseCursor: rowMouseCursor,
child: contents,
);
}
Expand All @@ -788,6 +821,7 @@ class DataTable extends StatelessWidget {
required bool sorted,
required bool ascending,
required MaterialStateProperty<Color?>? overlayColor,
required MouseCursor? mouseCursor,
}) {
final ThemeData themeData = Theme.of(context);
final DataTableThemeData dataTableTheme = DataTableTheme.of(context);
Expand Down Expand Up @@ -838,6 +872,7 @@ class DataTable extends StatelessWidget {
label = InkWell(
onTap: onSort,
overlayColor: overlayColor,
mouseCursor: mouseCursor,
child: label,
);
return label;
Expand All @@ -858,6 +893,7 @@ class DataTable extends StatelessWidget {
required GestureTapCancelCallback? onTapCancel,
required MaterialStateProperty<Color?>? overlayColor,
required GestureLongPressCallback? onRowLongPress,
required MouseCursor? mouseCursor,
}) {
final ThemeData themeData = Theme.of(context);
final DataTableThemeData dataTableTheme = DataTableTheme.of(context);
Expand Down Expand Up @@ -912,6 +948,7 @@ class DataTable extends StatelessWidget {
onTap: onSelectChanged,
onLongPress: onRowLongPress,
overlayColor: overlayColor,
mouseCursor: mouseCursor,
child: label,
);
}
Expand Down Expand Up @@ -1014,12 +1051,17 @@ class DataTable extends StatelessWidget {
);
rowIndex = 1;
for (final DataRow row in rows) {
final Set<MaterialState> states = <MaterialState>{
if (row.selected)
MaterialState.selected,
};
tableRows[rowIndex].children[0] = _buildCheckbox(
context: context,
checked: row.selected,
onRowTap: row.onSelectChanged == null ? null : () => row.onSelectChanged?.call(!row.selected),
onCheckboxChanged: row.onSelectChanged,
overlayColor: row.color ?? effectiveDataRowColor,
rowMouseCursor: row.mouseCursor?.resolve(states) ?? dataTableTheme.dataRowCursor?.resolve(states),
tristate: false,
);
rowIndex += 1;
Expand Down Expand Up @@ -1057,6 +1099,10 @@ class DataTable extends StatelessWidget {
} else {
tableColumns[displayColumnIndex] = const IntrinsicColumnWidth();
}
final Set<MaterialState> headerStates = <MaterialState>{
if (column.onSort == null)
MaterialState.disabled,
};
tableRows[0].children[displayColumnIndex] = _buildHeadingCell(
context: context,
padding: padding,
Expand All @@ -1067,9 +1113,14 @@ class DataTable extends StatelessWidget {
sorted: dataColumnIndex == sortColumnIndex,
ascending: sortAscending,
overlayColor: effectiveHeadingRowColor,
mouseCursor: column.mouseCursor?.resolve(headerStates) ?? dataTableTheme.headingCellCursor?.resolve(headerStates),
);
rowIndex = 1;
for (final DataRow row in rows) {
final Set<MaterialState> states = <MaterialState>{
if (row.selected)
MaterialState.selected,
};
final DataCell cell = row.cells[dataColumnIndex];
tableRows[rowIndex].children[displayColumnIndex] = _buildDataCell(
context: context,
Expand All @@ -1086,6 +1137,7 @@ class DataTable extends StatelessWidget {
onSelectChanged: row.onSelectChanged == null ? null : () => row.onSelectChanged?.call(!row.selected),
overlayColor: row.color ?? effectiveDataRowColor,
onRowLongPress: row.onLongPress,
mouseCursor: row.mouseCursor?.resolve(states) ?? dataTableTheme.dataRowCursor?.resolve(states),
);
rowIndex += 1;
}
Expand Down Expand Up @@ -1138,6 +1190,7 @@ class TableRowInkWell extends InkResponse {
super.onLongPress,
super.onHighlightChanged,
super.overlayColor,
super.mouseCursor,
}) : super(
containedInkWell: true,
highlightShape: BoxShape.rectangle,
Expand Down
22 changes: 21 additions & 1 deletion packages/flutter/lib/src/material/data_table_theme.dart
Expand Up @@ -55,6 +55,8 @@ class DataTableThemeData with Diagnosticable {
this.columnSpacing,
this.dividerThickness,
this.checkboxHorizontalMargin,
this.headingCellCursor,
this.dataRowCursor,
}) : assert(dataRowMinHeight == null || dataRowMaxHeight == null || dataRowMaxHeight >= dataRowMinHeight),
assert(dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null),
'dataRowHeight ($dataRowHeight) must not be set if dataRowMinHeight ($dataRowMinHeight) or dataRowMaxHeight ($dataRowMaxHeight) are set.'),
Expand Down Expand Up @@ -106,6 +108,12 @@ class DataTableThemeData with Diagnosticable {
/// {@macro flutter.material.dataTable.checkboxHorizontalMargin}
final double? checkboxHorizontalMargin;

/// If specified, overrides the default value of [DataColumn.mouseCursor].
final MaterialStateProperty<MouseCursor?>? headingCellCursor;

/// If specified, overrides the default value of [DataRow.mouseCursor].
final MaterialStateProperty<MouseCursor?>? dataRowCursor;

/// Creates a copy of this object but with the given fields replaced with the
/// new values.
DataTableThemeData copyWith({
Expand All @@ -126,6 +134,8 @@ class DataTableThemeData with Diagnosticable {
double? columnSpacing,
double? dividerThickness,
double? checkboxHorizontalMargin,
MaterialStateProperty<MouseCursor?>? headingCellCursor,
MaterialStateProperty<MouseCursor?>? dataRowCursor,
}) {
return DataTableThemeData(
decoration: decoration ?? this.decoration,
Expand All @@ -141,6 +151,8 @@ class DataTableThemeData with Diagnosticable {
columnSpacing: columnSpacing ?? this.columnSpacing,
dividerThickness: dividerThickness ?? this.dividerThickness,
checkboxHorizontalMargin: checkboxHorizontalMargin ?? this.checkboxHorizontalMargin,
headingCellCursor: headingCellCursor ?? this.headingCellCursor,
dataRowCursor: dataRowCursor ?? this.dataRowCursor,
);
}

Expand All @@ -166,6 +178,8 @@ class DataTableThemeData with Diagnosticable {
columnSpacing: lerpDouble(a.columnSpacing, b.columnSpacing, t),
dividerThickness: lerpDouble(a.dividerThickness, b.dividerThickness, t),
checkboxHorizontalMargin: lerpDouble(a.checkboxHorizontalMargin, b.checkboxHorizontalMargin, t),
headingCellCursor: t < 0.5 ? a.headingCellCursor : b.headingCellCursor,
dataRowCursor: t < 0.5 ? a.dataRowCursor : b.dataRowCursor,
);
}

Expand All @@ -183,6 +197,8 @@ class DataTableThemeData with Diagnosticable {
columnSpacing,
dividerThickness,
checkboxHorizontalMargin,
headingCellCursor,
dataRowCursor,
);

@override
Expand All @@ -205,7 +221,9 @@ class DataTableThemeData with Diagnosticable {
&& other.horizontalMargin == horizontalMargin
&& other.columnSpacing == columnSpacing
&& other.dividerThickness == dividerThickness
&& other.checkboxHorizontalMargin == checkboxHorizontalMargin;
&& other.checkboxHorizontalMargin == checkboxHorizontalMargin
&& other.headingCellCursor == headingCellCursor
&& other.dataRowCursor == dataRowCursor;
}

@override
Expand All @@ -223,6 +241,8 @@ class DataTableThemeData with Diagnosticable {
properties.add(DoubleProperty('columnSpacing', columnSpacing, defaultValue: null));
properties.add(DoubleProperty('dividerThickness', dividerThickness, defaultValue: null));
properties.add(DoubleProperty('checkboxHorizontalMargin', checkboxHorizontalMargin, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>?>('headingCellCursor', headingCellCursor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>?>('dataRowCursor', dataRowCursor, defaultValue: null));
}
}

Expand Down

0 comments on commit f7fb14e

Please sign in to comment.