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

Add customizable mouse cursor to `DataTable` (#123128)

parent 25e692cb
...@@ -44,6 +44,7 @@ class DataColumn { ...@@ -44,6 +44,7 @@ class DataColumn {
this.tooltip, this.tooltip,
this.numeric = false, this.numeric = false,
this.onSort, this.onSort,
this.mouseCursor,
}); });
/// The column heading. /// The column heading.
...@@ -85,6 +86,20 @@ class DataColumn { ...@@ -85,6 +86,20 @@ class DataColumn {
final DataColumnSortCallback? onSort; final DataColumnSortCallback? onSort;
bool get _debugInteractive => onSort != null; 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]. /// Row configuration and cell data for a [DataTable].
...@@ -106,6 +121,7 @@ class DataRow { ...@@ -106,6 +121,7 @@ class DataRow {
this.onSelectChanged, this.onSelectChanged,
this.onLongPress, this.onLongPress,
this.color, this.color,
this.mouseCursor,
required this.cells, required this.cells,
}); });
...@@ -119,6 +135,7 @@ class DataRow { ...@@ -119,6 +135,7 @@ class DataRow {
this.onSelectChanged, this.onSelectChanged,
this.onLongPress, this.onLongPress,
this.color, this.color,
this.mouseCursor,
required this.cells, required this.cells,
}) : key = ValueKey<int?>(index); }) : key = ValueKey<int?>(index);
...@@ -205,6 +222,20 @@ class DataRow { ...@@ -205,6 +222,20 @@ class DataRow {
/// <https://material.io/design/interaction/states.html#anatomy>. /// <https://material.io/design/interaction/states.html#anatomy>.
final MaterialStateProperty<Color?>? color; 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); bool get _debugInteractive => onSelectChanged != null || cells.any((DataCell cell) => cell._debugInteractive);
} }
...@@ -738,6 +769,7 @@ class DataTable extends StatelessWidget { ...@@ -738,6 +769,7 @@ class DataTable extends StatelessWidget {
required ValueChanged<bool?>? onCheckboxChanged, required ValueChanged<bool?>? onCheckboxChanged,
required MaterialStateProperty<Color?>? overlayColor, required MaterialStateProperty<Color?>? overlayColor,
required bool tristate, required bool tristate,
MouseCursor? rowMouseCursor,
}) { }) {
final ThemeData themeData = Theme.of(context); final ThemeData themeData = Theme.of(context);
final double effectiveHorizontalMargin = horizontalMargin final double effectiveHorizontalMargin = horizontalMargin
...@@ -769,6 +801,7 @@ class DataTable extends StatelessWidget { ...@@ -769,6 +801,7 @@ class DataTable extends StatelessWidget {
contents = TableRowInkWell( contents = TableRowInkWell(
onTap: onRowTap, onTap: onRowTap,
overlayColor: overlayColor, overlayColor: overlayColor,
mouseCursor: rowMouseCursor,
child: contents, child: contents,
); );
} }
...@@ -788,6 +821,7 @@ class DataTable extends StatelessWidget { ...@@ -788,6 +821,7 @@ class DataTable extends StatelessWidget {
required bool sorted, required bool sorted,
required bool ascending, required bool ascending,
required MaterialStateProperty<Color?>? overlayColor, required MaterialStateProperty<Color?>? overlayColor,
required MouseCursor? mouseCursor,
}) { }) {
final ThemeData themeData = Theme.of(context); final ThemeData themeData = Theme.of(context);
final DataTableThemeData dataTableTheme = DataTableTheme.of(context); final DataTableThemeData dataTableTheme = DataTableTheme.of(context);
...@@ -838,6 +872,7 @@ class DataTable extends StatelessWidget { ...@@ -838,6 +872,7 @@ class DataTable extends StatelessWidget {
label = InkWell( label = InkWell(
onTap: onSort, onTap: onSort,
overlayColor: overlayColor, overlayColor: overlayColor,
mouseCursor: mouseCursor,
child: label, child: label,
); );
return label; return label;
...@@ -858,6 +893,7 @@ class DataTable extends StatelessWidget { ...@@ -858,6 +893,7 @@ class DataTable extends StatelessWidget {
required GestureTapCancelCallback? onTapCancel, required GestureTapCancelCallback? onTapCancel,
required MaterialStateProperty<Color?>? overlayColor, required MaterialStateProperty<Color?>? overlayColor,
required GestureLongPressCallback? onRowLongPress, required GestureLongPressCallback? onRowLongPress,
required MouseCursor? mouseCursor,
}) { }) {
final ThemeData themeData = Theme.of(context); final ThemeData themeData = Theme.of(context);
final DataTableThemeData dataTableTheme = DataTableTheme.of(context); final DataTableThemeData dataTableTheme = DataTableTheme.of(context);
...@@ -912,6 +948,7 @@ class DataTable extends StatelessWidget { ...@@ -912,6 +948,7 @@ class DataTable extends StatelessWidget {
onTap: onSelectChanged, onTap: onSelectChanged,
onLongPress: onRowLongPress, onLongPress: onRowLongPress,
overlayColor: overlayColor, overlayColor: overlayColor,
mouseCursor: mouseCursor,
child: label, child: label,
); );
} }
...@@ -1014,12 +1051,17 @@ class DataTable extends StatelessWidget { ...@@ -1014,12 +1051,17 @@ class DataTable extends StatelessWidget {
); );
rowIndex = 1; rowIndex = 1;
for (final DataRow row in rows) { for (final DataRow row in rows) {
final Set<MaterialState> states = <MaterialState>{
if (row.selected)
MaterialState.selected,
};
tableRows[rowIndex].children[0] = _buildCheckbox( tableRows[rowIndex].children[0] = _buildCheckbox(
context: context, context: context,
checked: row.selected, checked: row.selected,
onRowTap: row.onSelectChanged == null ? null : () => row.onSelectChanged?.call(!row.selected), onRowTap: row.onSelectChanged == null ? null : () => row.onSelectChanged?.call(!row.selected),
onCheckboxChanged: row.onSelectChanged, onCheckboxChanged: row.onSelectChanged,
overlayColor: row.color ?? effectiveDataRowColor, overlayColor: row.color ?? effectiveDataRowColor,
rowMouseCursor: row.mouseCursor?.resolve(states) ?? dataTableTheme.dataRowCursor?.resolve(states),
tristate: false, tristate: false,
); );
rowIndex += 1; rowIndex += 1;
...@@ -1057,6 +1099,10 @@ class DataTable extends StatelessWidget { ...@@ -1057,6 +1099,10 @@ class DataTable extends StatelessWidget {
} else { } else {
tableColumns[displayColumnIndex] = const IntrinsicColumnWidth(); tableColumns[displayColumnIndex] = const IntrinsicColumnWidth();
} }
final Set<MaterialState> headerStates = <MaterialState>{
if (column.onSort == null)
MaterialState.disabled,
};
tableRows[0].children[displayColumnIndex] = _buildHeadingCell( tableRows[0].children[displayColumnIndex] = _buildHeadingCell(
context: context, context: context,
padding: padding, padding: padding,
...@@ -1067,9 +1113,14 @@ class DataTable extends StatelessWidget { ...@@ -1067,9 +1113,14 @@ class DataTable extends StatelessWidget {
sorted: dataColumnIndex == sortColumnIndex, sorted: dataColumnIndex == sortColumnIndex,
ascending: sortAscending, ascending: sortAscending,
overlayColor: effectiveHeadingRowColor, overlayColor: effectiveHeadingRowColor,
mouseCursor: column.mouseCursor?.resolve(headerStates) ?? dataTableTheme.headingCellCursor?.resolve(headerStates),
); );
rowIndex = 1; rowIndex = 1;
for (final DataRow row in rows) { for (final DataRow row in rows) {
final Set<MaterialState> states = <MaterialState>{
if (row.selected)
MaterialState.selected,
};
final DataCell cell = row.cells[dataColumnIndex]; final DataCell cell = row.cells[dataColumnIndex];
tableRows[rowIndex].children[displayColumnIndex] = _buildDataCell( tableRows[rowIndex].children[displayColumnIndex] = _buildDataCell(
context: context, context: context,
...@@ -1086,6 +1137,7 @@ class DataTable extends StatelessWidget { ...@@ -1086,6 +1137,7 @@ class DataTable extends StatelessWidget {
onSelectChanged: row.onSelectChanged == null ? null : () => row.onSelectChanged?.call(!row.selected), onSelectChanged: row.onSelectChanged == null ? null : () => row.onSelectChanged?.call(!row.selected),
overlayColor: row.color ?? effectiveDataRowColor, overlayColor: row.color ?? effectiveDataRowColor,
onRowLongPress: row.onLongPress, onRowLongPress: row.onLongPress,
mouseCursor: row.mouseCursor?.resolve(states) ?? dataTableTheme.dataRowCursor?.resolve(states),
); );
rowIndex += 1; rowIndex += 1;
} }
...@@ -1138,6 +1190,7 @@ class TableRowInkWell extends InkResponse { ...@@ -1138,6 +1190,7 @@ class TableRowInkWell extends InkResponse {
super.onLongPress, super.onLongPress,
super.onHighlightChanged, super.onHighlightChanged,
super.overlayColor, super.overlayColor,
super.mouseCursor,
}) : super( }) : super(
containedInkWell: true, containedInkWell: true,
highlightShape: BoxShape.rectangle, highlightShape: BoxShape.rectangle,
......
...@@ -55,6 +55,8 @@ class DataTableThemeData with Diagnosticable { ...@@ -55,6 +55,8 @@ class DataTableThemeData with Diagnosticable {
this.columnSpacing, this.columnSpacing,
this.dividerThickness, this.dividerThickness,
this.checkboxHorizontalMargin, this.checkboxHorizontalMargin,
this.headingCellCursor,
this.dataRowCursor,
}) : assert(dataRowMinHeight == null || dataRowMaxHeight == null || dataRowMaxHeight >= dataRowMinHeight), }) : assert(dataRowMinHeight == null || dataRowMaxHeight == null || dataRowMaxHeight >= dataRowMinHeight),
assert(dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null), assert(dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null),
'dataRowHeight ($dataRowHeight) must not be set if dataRowMinHeight ($dataRowMinHeight) or dataRowMaxHeight ($dataRowMaxHeight) are set.'), 'dataRowHeight ($dataRowHeight) must not be set if dataRowMinHeight ($dataRowMinHeight) or dataRowMaxHeight ($dataRowMaxHeight) are set.'),
...@@ -106,6 +108,12 @@ class DataTableThemeData with Diagnosticable { ...@@ -106,6 +108,12 @@ class DataTableThemeData with Diagnosticable {
/// {@macro flutter.material.dataTable.checkboxHorizontalMargin} /// {@macro flutter.material.dataTable.checkboxHorizontalMargin}
final double? 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 /// Creates a copy of this object but with the given fields replaced with the
/// new values. /// new values.
DataTableThemeData copyWith({ DataTableThemeData copyWith({
...@@ -126,6 +134,8 @@ class DataTableThemeData with Diagnosticable { ...@@ -126,6 +134,8 @@ class DataTableThemeData with Diagnosticable {
double? columnSpacing, double? columnSpacing,
double? dividerThickness, double? dividerThickness,
double? checkboxHorizontalMargin, double? checkboxHorizontalMargin,
MaterialStateProperty<MouseCursor?>? headingCellCursor,
MaterialStateProperty<MouseCursor?>? dataRowCursor,
}) { }) {
return DataTableThemeData( return DataTableThemeData(
decoration: decoration ?? this.decoration, decoration: decoration ?? this.decoration,
...@@ -141,6 +151,8 @@ class DataTableThemeData with Diagnosticable { ...@@ -141,6 +151,8 @@ class DataTableThemeData with Diagnosticable {
columnSpacing: columnSpacing ?? this.columnSpacing, columnSpacing: columnSpacing ?? this.columnSpacing,
dividerThickness: dividerThickness ?? this.dividerThickness, dividerThickness: dividerThickness ?? this.dividerThickness,
checkboxHorizontalMargin: checkboxHorizontalMargin ?? this.checkboxHorizontalMargin, checkboxHorizontalMargin: checkboxHorizontalMargin ?? this.checkboxHorizontalMargin,
headingCellCursor: headingCellCursor ?? this.headingCellCursor,
dataRowCursor: dataRowCursor ?? this.dataRowCursor,
); );
} }
...@@ -166,6 +178,8 @@ class DataTableThemeData with Diagnosticable { ...@@ -166,6 +178,8 @@ class DataTableThemeData with Diagnosticable {
columnSpacing: lerpDouble(a.columnSpacing, b.columnSpacing, t), columnSpacing: lerpDouble(a.columnSpacing, b.columnSpacing, t),
dividerThickness: lerpDouble(a.dividerThickness, b.dividerThickness, t), dividerThickness: lerpDouble(a.dividerThickness, b.dividerThickness, t),
checkboxHorizontalMargin: lerpDouble(a.checkboxHorizontalMargin, b.checkboxHorizontalMargin, t), checkboxHorizontalMargin: lerpDouble(a.checkboxHorizontalMargin, b.checkboxHorizontalMargin, t),
headingCellCursor: t < 0.5 ? a.headingCellCursor : b.headingCellCursor,
dataRowCursor: t < 0.5 ? a.dataRowCursor : b.dataRowCursor,
); );
} }
...@@ -183,6 +197,8 @@ class DataTableThemeData with Diagnosticable { ...@@ -183,6 +197,8 @@ class DataTableThemeData with Diagnosticable {
columnSpacing, columnSpacing,
dividerThickness, dividerThickness,
checkboxHorizontalMargin, checkboxHorizontalMargin,
headingCellCursor,
dataRowCursor,
); );
@override @override
...@@ -205,7 +221,9 @@ class DataTableThemeData with Diagnosticable { ...@@ -205,7 +221,9 @@ class DataTableThemeData with Diagnosticable {
&& other.horizontalMargin == horizontalMargin && other.horizontalMargin == horizontalMargin
&& other.columnSpacing == columnSpacing && other.columnSpacing == columnSpacing
&& other.dividerThickness == dividerThickness && other.dividerThickness == dividerThickness
&& other.checkboxHorizontalMargin == checkboxHorizontalMargin; && other.checkboxHorizontalMargin == checkboxHorizontalMargin
&& other.headingCellCursor == headingCellCursor
&& other.dataRowCursor == dataRowCursor;
} }
@override @override
...@@ -223,6 +241,8 @@ class DataTableThemeData with Diagnosticable { ...@@ -223,6 +241,8 @@ class DataTableThemeData with Diagnosticable {
properties.add(DoubleProperty('columnSpacing', columnSpacing, defaultValue: null)); properties.add(DoubleProperty('columnSpacing', columnSpacing, defaultValue: null));
properties.add(DoubleProperty('dividerThickness', dividerThickness, defaultValue: null)); properties.add(DoubleProperty('dividerThickness', dividerThickness, defaultValue: null));
properties.add(DoubleProperty('checkboxHorizontalMargin', checkboxHorizontalMargin, 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));
} }
} }
......
...@@ -9,6 +9,7 @@ import 'dart:math' as math; ...@@ -9,6 +9,7 @@ import 'dart:math' as math;
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:vector_math/vector_math_64.dart' show Matrix3; import 'package:vector_math/vector_math_64.dart' show Matrix3;
...@@ -2072,4 +2073,169 @@ void main() { ...@@ -2072,4 +2073,169 @@ void main() {
expect(() => createDataTable(dataRowHeight: 1.0, dataRowMinHeight: 2.0), throwsA(predicate((AssertionError e) => expect(() => createDataTable(dataRowHeight: 1.0, dataRowMinHeight: 2.0), throwsA(predicate((AssertionError e) =>
e.toString().contains('dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null)')))); e.toString().contains('dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null)'))));
}); });
testWidgets('Heading cell cursor resolves MaterialStateMouseCursor correctly', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DataTable(
sortColumnIndex: 0,
columns: <DataColumn>[
// This column can be sorted.
DataColumn(
mouseCursor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return SystemMouseCursors.forbidden;
}
return SystemMouseCursors.copy;
}),
onSort: (int columnIndex, bool ascending) {},
label: const Text('A'),
),
// This column cannot be sorted.
DataColumn(
mouseCursor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return SystemMouseCursors.forbidden;
}
return SystemMouseCursors.copy;
}),
label: const Text('B'),
),
],
rows: const <DataRow>[
DataRow(
cells: <DataCell>[
DataCell(Text('Data 1')),
DataCell(Text('Data 2')),
],
),
DataRow(
cells: <DataCell>[
DataCell(Text('Data 3')),
DataCell(Text('Data 4')),
],
),
],
),
),
),
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
await gesture.addPointer(location: tester.getCenter(find.text('A')));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.copy);
await gesture.moveTo(tester.getCenter(find.text('B')));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
});
testWidgets('DataRow cursor resolves MaterialStateMouseCursor correctly', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DataTable(
sortColumnIndex: 0,
columns: <DataColumn>[
DataColumn(
label: const Text('A'),
onSort: (int columnIndex, bool ascending) {},
),
const DataColumn(label: Text('B')),
],
rows: <DataRow>[
// This row can be selected.
DataRow(
mouseCursor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return SystemMouseCursors.copy;
}
return SystemMouseCursors.forbidden;
}),
onSelectChanged: (bool? selected) {},
cells: const <DataCell>[
DataCell(Text('Data 1')),
DataCell(Text('Data 2')),
],
),
// This row is selected.
DataRow(
selected: true,
onSelectChanged: (bool? selected) {},
mouseCursor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return SystemMouseCursors.copy;
}
return SystemMouseCursors.forbidden;
}),
cells: const <DataCell>[
DataCell(Text('Data 3')),
DataCell(Text('Data 4')),
],
),
],
),
),
),
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
await gesture.addPointer(location: tester.getCenter(find.text('Data 1')));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
await gesture.moveTo(tester.getCenter(find.text('Data 3')));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.copy);
});
testWidgets("DataRow cursor doesn't update checkbox cursor", (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DataTable(
sortColumnIndex: 0,
columns: <DataColumn>[
DataColumn(
label: const Text('A'),
onSort: (int columnIndex, bool ascending) {},
),
const DataColumn(label: Text('B')),
],
rows: <DataRow>[
DataRow(
onSelectChanged: (bool? selected) {},
mouseCursor: const MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.copy),
cells: const <DataCell>[
DataCell(Text('Data')),
DataCell(Text('Data 2')),
],
),
],
),
),
),
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
await gesture.addPointer(location: tester.getCenter(find.byType(Checkbox).last));
await tester.pump();
// Test that the checkbox cursor is not changed.
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
await gesture.moveTo(tester.getCenter(find.text('Data')));
await tester.pump();
// Test that cursor is updated for the row.
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.copy);
});
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -32,6 +33,8 @@ void main() { ...@@ -32,6 +33,8 @@ void main() {
expect(themeData.columnSpacing, null); expect(themeData.columnSpacing, null);
expect(themeData.dividerThickness, null); expect(themeData.dividerThickness, null);
expect(themeData.checkboxHorizontalMargin, null); expect(themeData.checkboxHorizontalMargin, null);
expect(themeData.headingCellCursor, null);
expect(themeData.dataRowCursor, null);
const DataTableTheme theme = DataTableTheme(data: DataTableThemeData(), child: SizedBox()); const DataTableTheme theme = DataTableTheme(data: DataTableThemeData(), child: SizedBox());
expect(theme.data.decoration, null); expect(theme.data.decoration, null);
...@@ -47,6 +50,8 @@ void main() { ...@@ -47,6 +50,8 @@ void main() {
expect(theme.data.columnSpacing, null); expect(theme.data.columnSpacing, null);
expect(theme.data.dividerThickness, null); expect(theme.data.dividerThickness, null);
expect(theme.data.checkboxHorizontalMargin, null); expect(theme.data.checkboxHorizontalMargin, null);
expect(theme.data.headingCellCursor, null);
expect(theme.data.dataRowCursor, null);
}); });
testWidgets('Default DataTableThemeData debugFillProperties', (WidgetTester tester) async { testWidgets('Default DataTableThemeData debugFillProperties', (WidgetTester tester) async {
...@@ -80,6 +85,8 @@ void main() { ...@@ -80,6 +85,8 @@ void main() {
columnSpacing: 4.0, columnSpacing: 4.0,
dividerThickness: 5.0, dividerThickness: 5.0,
checkboxHorizontalMargin: 6.0, checkboxHorizontalMargin: 6.0,
headingCellCursor: const MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.grab),
dataRowCursor: const MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.forbidden),
).debugFillProperties(builder); ).debugFillProperties(builder);
final List<String> description = builder.properties final List<String> description = builder.properties
...@@ -99,6 +106,8 @@ void main() { ...@@ -99,6 +106,8 @@ void main() {
expect(description[9], 'columnSpacing: 4.0'); expect(description[9], 'columnSpacing: 4.0');
expect(description[10], 'dividerThickness: 5.0'); expect(description[10], 'dividerThickness: 5.0');
expect(description[11], 'checkboxHorizontalMargin: 6.0'); expect(description[11], 'checkboxHorizontalMargin: 6.0');
expect(description[12], 'headingCellCursor: MaterialStatePropertyAll(SystemMouseCursor(grab))');
expect(description[13], 'dataRowCursor: MaterialStatePropertyAll(SystemMouseCursor(forbidden))');
}); });
testWidgets('DataTable is themeable', (WidgetTester tester) async { testWidgets('DataTable is themeable', (WidgetTester tester) async {
...@@ -112,6 +121,8 @@ void main() { ...@@ -112,6 +121,8 @@ void main() {
const double horizontalMargin = 3.0; const double horizontalMargin = 3.0;
const double columnSpacing = 4.0; const double columnSpacing = 4.0;
const double dividerThickness = 5.0; const double dividerThickness = 5.0;
const MaterialStateProperty<MouseCursor> headingCellCursor = MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.grab);
const MaterialStateProperty<MouseCursor> dataRowCursor = MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.forbidden);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -128,11 +139,14 @@ void main() { ...@@ -128,11 +139,14 @@ void main() {
horizontalMargin: horizontalMargin, horizontalMargin: horizontalMargin,
columnSpacing: columnSpacing, columnSpacing: columnSpacing,
dividerThickness: dividerThickness, dividerThickness: dividerThickness,
headingCellCursor: headingCellCursor,
dataRowCursor: dataRowCursor,
), ),
), ),
home: Scaffold( home: Scaffold(
body: DataTable( body: DataTable(
sortColumnIndex: 0, sortColumnIndex: 0,
showCheckboxColumn: false,
columns: <DataColumn>[ columns: <DataColumn>[
DataColumn( DataColumn(
label: const Text('A'), label: const Text('A'),
...@@ -140,11 +154,14 @@ void main() { ...@@ -140,11 +154,14 @@ void main() {
), ),
const DataColumn(label: Text('B')), const DataColumn(label: Text('B')),
], ],
rows: const <DataRow>[ rows: <DataRow>[
DataRow(cells: <DataCell>[ DataRow(
cells: const <DataCell>[
DataCell(Text('Data')), DataCell(Text('Data')),
DataCell(Text('Data 2')), DataCell(Text('Data 2')),
]), ],
onSelectChanged: (bool? value) { },
),
], ],
), ),
), ),
...@@ -167,6 +184,17 @@ void main() { ...@@ -167,6 +184,17 @@ void main() {
expect(tester.getSize(_findFirstContainerFor('A')).height, headingRowHeight); expect(tester.getSize(_findFirstContainerFor('A')).height, headingRowHeight);
expect(tester.getTopLeft(find.text('A')).dx, horizontalMargin); expect(tester.getTopLeft(find.text('A')).dx, horizontalMargin);
expect(tester.getTopLeft(find.text('Data 2')).dx - tester.getTopRight(find.text('Data')).dx, columnSpacing); expect(tester.getTopLeft(find.text('Data 2')).dx - tester.getTopRight(find.text('Data')).dx, columnSpacing);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
await gesture.addPointer(location: tester.getCenter(find.text('A')));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.grab);
await gesture.moveTo(tester.getCenter(find.text('Data')));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
}); });
testWidgets('DataTable is themeable - separate test for deprecated dataRowHeight', (WidgetTester tester) async { testWidgets('DataTable is themeable - separate test for deprecated dataRowHeight', (WidgetTester tester) async {
...@@ -214,6 +242,8 @@ void main() { ...@@ -214,6 +242,8 @@ void main() {
const double themeHorizontalMargin = 2.0; const double themeHorizontalMargin = 2.0;
const double themeColumnSpacing = 3.0; const double themeColumnSpacing = 3.0;
const double themeDividerThickness = 4.0; const double themeDividerThickness = 4.0;
const MaterialStateProperty<MouseCursor> themeHeadingCellCursor = MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.copy);
const MaterialStateProperty<MouseCursor> themeDataRowCursor = MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.copy);
const BoxDecoration decoration = BoxDecoration(color: Color(0xfffffff0)); const BoxDecoration decoration = BoxDecoration(color: Color(0xfffffff0));
const MaterialStateProperty<Color> dataRowColor = MaterialStatePropertyAll<Color>(Color(0xfffffff1)); const MaterialStateProperty<Color> dataRowColor = MaterialStatePropertyAll<Color>(Color(0xfffffff1));
...@@ -225,6 +255,8 @@ void main() { ...@@ -225,6 +255,8 @@ void main() {
const double horizontalMargin = 3.0; const double horizontalMargin = 3.0;
const double columnSpacing = 4.0; const double columnSpacing = 4.0;
const double dividerThickness = 5.0; const double dividerThickness = 5.0;
const MaterialStateProperty<MouseCursor> headingCellCursor = MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.forbidden);
const MaterialStateProperty<MouseCursor> dataRowCursor = MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.forbidden);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -241,10 +273,13 @@ void main() { ...@@ -241,10 +273,13 @@ void main() {
horizontalMargin: themeHorizontalMargin, horizontalMargin: themeHorizontalMargin,
columnSpacing: themeColumnSpacing, columnSpacing: themeColumnSpacing,
dividerThickness: themeDividerThickness, dividerThickness: themeDividerThickness,
headingCellCursor: themeHeadingCellCursor,
dataRowCursor: themeDataRowCursor,
), ),
), ),
home: Scaffold( home: Scaffold(
body: DataTable( body: DataTable(
showCheckboxColumn: false,
decoration: decoration, decoration: decoration,
dataRowColor: dataRowColor, dataRowColor: dataRowColor,
dataRowMinHeight: minMaxDataRowHeight, dataRowMinHeight: minMaxDataRowHeight,
...@@ -260,15 +295,20 @@ void main() { ...@@ -260,15 +295,20 @@ void main() {
columns: <DataColumn>[ columns: <DataColumn>[
DataColumn( DataColumn(
label: const Text('A'), label: const Text('A'),
mouseCursor: headingCellCursor,
onSort: (int columnIndex, bool ascending) {}, onSort: (int columnIndex, bool ascending) {},
), ),
const DataColumn(label: Text('B')), const DataColumn(label: Text('B')),
], ],
rows: const <DataRow>[ rows: <DataRow>[
DataRow(cells: <DataCell>[ DataRow(
mouseCursor: dataRowCursor,
onSelectChanged: (bool? selected) {},
cells: const <DataCell>[
DataCell(Text('Data')), DataCell(Text('Data')),
DataCell(Text('Data 2')), DataCell(Text('Data 2')),
]), ],
),
], ],
), ),
), ),
...@@ -291,6 +331,17 @@ void main() { ...@@ -291,6 +331,17 @@ void main() {
expect(tester.getSize(_findFirstContainerFor('A')).height, headingRowHeight); expect(tester.getSize(_findFirstContainerFor('A')).height, headingRowHeight);
expect(tester.getTopLeft(find.text('A')).dx, horizontalMargin); expect(tester.getTopLeft(find.text('A')).dx, horizontalMargin);
expect(tester.getTopLeft(find.text('Data 2')).dx - tester.getTopRight(find.text('Data')).dx, columnSpacing); expect(tester.getTopLeft(find.text('Data 2')).dx - tester.getTopRight(find.text('Data')).dx, columnSpacing);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
await gesture.addPointer(location: tester.getCenter(find.text('A')));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), headingCellCursor.resolve(<MaterialState>{}));
await gesture.moveTo(tester.getCenter(find.text('Data')));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), dataRowCursor.resolve(<MaterialState>{}));
}); });
testWidgets('DataTable properties are taken over the theme values - separate test for deprecated dataRowHeight', (WidgetTester tester) async { testWidgets('DataTable properties are taken over the theme values - separate test for deprecated dataRowHeight', (WidgetTester tester) async {
...@@ -340,6 +391,8 @@ void main() { ...@@ -340,6 +391,8 @@ void main() {
const double globalThemeHorizontalMargin = 2.0; const double globalThemeHorizontalMargin = 2.0;
const double globalThemeColumnSpacing = 3.0; const double globalThemeColumnSpacing = 3.0;
const double globalThemeDividerThickness = 4.0; const double globalThemeDividerThickness = 4.0;
const MaterialStateProperty<MouseCursor> globalHeadingCellCursor = MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.allScroll);
const MaterialStateProperty<MouseCursor> globalDataRowCursor = MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.allScroll);
const BoxDecoration localThemeDecoration = BoxDecoration(color: Color(0xfffffff0)); const BoxDecoration localThemeDecoration = BoxDecoration(color: Color(0xfffffff0));
const MaterialStateProperty<Color> localThemeDataRowColor = MaterialStatePropertyAll<Color>(Color(0xfffffff1)); const MaterialStateProperty<Color> localThemeDataRowColor = MaterialStatePropertyAll<Color>(Color(0xfffffff1));
...@@ -351,6 +404,8 @@ void main() { ...@@ -351,6 +404,8 @@ void main() {
const double localThemeHorizontalMargin = 3.0; const double localThemeHorizontalMargin = 3.0;
const double localThemeColumnSpacing = 4.0; const double localThemeColumnSpacing = 4.0;
const double localThemeDividerThickness = 5.0; const double localThemeDividerThickness = 5.0;
const MaterialStateProperty<MouseCursor> localHeadingCellCursor = MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.move);
const MaterialStateProperty<MouseCursor> localDataRowCursor = MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.move);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -367,6 +422,8 @@ void main() { ...@@ -367,6 +422,8 @@ void main() {
horizontalMargin: globalThemeHorizontalMargin, horizontalMargin: globalThemeHorizontalMargin,
columnSpacing: globalThemeColumnSpacing, columnSpacing: globalThemeColumnSpacing,
dividerThickness: globalThemeDividerThickness, dividerThickness: globalThemeDividerThickness,
headingCellCursor: globalHeadingCellCursor,
dataRowCursor: globalDataRowCursor,
), ),
), ),
home: Scaffold( home: Scaffold(
...@@ -383,8 +440,11 @@ void main() { ...@@ -383,8 +440,11 @@ void main() {
horizontalMargin: localThemeHorizontalMargin, horizontalMargin: localThemeHorizontalMargin,
columnSpacing: localThemeColumnSpacing, columnSpacing: localThemeColumnSpacing,
dividerThickness: localThemeDividerThickness, dividerThickness: localThemeDividerThickness,
headingCellCursor: localHeadingCellCursor,
dataRowCursor: localDataRowCursor,
), ),
child: DataTable( child: DataTable(
showCheckboxColumn: false,
sortColumnIndex: 0, sortColumnIndex: 0,
columns: <DataColumn>[ columns: <DataColumn>[
DataColumn( DataColumn(
...@@ -393,11 +453,14 @@ void main() { ...@@ -393,11 +453,14 @@ void main() {
), ),
const DataColumn(label: Text('B')), const DataColumn(label: Text('B')),
], ],
rows: const <DataRow>[ rows: <DataRow>[
DataRow(cells: <DataCell>[ DataRow(
onSelectChanged: (bool? selected) {},
cells: const <DataCell>[
DataCell(Text('Data')), DataCell(Text('Data')),
DataCell(Text('Data 2')), DataCell(Text('Data 2')),
]), ],
),
], ],
), ),
), ),
...@@ -421,6 +484,17 @@ void main() { ...@@ -421,6 +484,17 @@ void main() {
expect(tester.getSize(_findFirstContainerFor('A')).height, localThemeHeadingRowHeight); expect(tester.getSize(_findFirstContainerFor('A')).height, localThemeHeadingRowHeight);
expect(tester.getTopLeft(find.text('A')).dx, localThemeHorizontalMargin); expect(tester.getTopLeft(find.text('A')).dx, localThemeHorizontalMargin);
expect(tester.getTopLeft(find.text('Data 2')).dx - tester.getTopRight(find.text('Data')).dx, localThemeColumnSpacing); expect(tester.getTopLeft(find.text('Data 2')).dx - tester.getTopRight(find.text('Data')).dx, localThemeColumnSpacing);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
await gesture.addPointer(location: tester.getCenter(find.text('A')));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), localHeadingCellCursor.resolve(<MaterialState>{}));
await gesture.moveTo(tester.getCenter(find.text('Data')));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), localDataRowCursor.resolve(<MaterialState>{}));
}); });
testWidgets('Local DataTableTheme can override global DataTableTheme - separate test for deprecated dataRowHeight', (WidgetTester tester) async { testWidgets('Local DataTableTheme can override global DataTableTheme - separate test for deprecated dataRowHeight', (WidgetTester tester) async {
...@@ -464,7 +538,6 @@ void main() { ...@@ -464,7 +538,6 @@ void main() {
}); });
} }
BoxDecoration _tableRowBoxDecoration({required WidgetTester tester, required int index}) { BoxDecoration _tableRowBoxDecoration({required WidgetTester tester, required int index}) {
final Table table = tester.widget(find.byType(Table)); final Table table = tester.widget(find.byType(Table));
final TableRow tableRow = table.children[index]; final TableRow tableRow = table.children[index];
......
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