Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
f7fb14ec
Unverified
Commit
f7fb14ec
authored
Mar 24, 2023
by
Taha Tesser
Committed by
GitHub
Mar 24, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add customizable mouse cursor to `DataTable` (#123128)
parent
25e692cb
Changes
4
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
329 additions
and
17 deletions
+329
-17
data_table.dart
packages/flutter/lib/src/material/data_table.dart
+53
-0
data_table_theme.dart
packages/flutter/lib/src/material/data_table_theme.dart
+21
-1
data_table_test.dart
packages/flutter/test/material/data_table_test.dart
+166
-0
data_table_theme_test.dart
packages/flutter/test/material/data_table_theme_test.dart
+89
-16
No files found.
packages/flutter/lib/src/material/data_table.dart
View file @
f7fb14ec
...
...
@@ -44,6 +44,7 @@ class DataColumn {
this
.
tooltip
,
this
.
numeric
=
false
,
this
.
onSort
,
this
.
mouseCursor
,
});
/// The column heading.
...
...
@@ -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].
...
...
@@ -106,6 +121,7 @@ class DataRow {
this
.
onSelectChanged
,
this
.
onLongPress
,
this
.
color
,
this
.
mouseCursor
,
required
this
.
cells
,
});
...
...
@@ -119,6 +135,7 @@ class DataRow {
this
.
onSelectChanged
,
this
.
onLongPress
,
this
.
color
,
this
.
mouseCursor
,
required
this
.
cells
,
})
:
key
=
ValueKey
<
int
?>(
index
);
...
...
@@ -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
);
}
...
...
@@ -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
...
...
@@ -769,6 +801,7 @@ class DataTable extends StatelessWidget {
contents
=
TableRowInkWell
(
onTap:
onRowTap
,
overlayColor:
overlayColor
,
mouseCursor:
rowMouseCursor
,
child:
contents
,
);
}
...
...
@@ -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
);
...
...
@@ -838,6 +872,7 @@ class DataTable extends StatelessWidget {
label
=
InkWell
(
onTap:
onSort
,
overlayColor:
overlayColor
,
mouseCursor:
mouseCursor
,
child:
label
,
);
return
label
;
...
...
@@ -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
);
...
...
@@ -912,6 +948,7 @@ class DataTable extends StatelessWidget {
onTap:
onSelectChanged
,
onLongPress:
onRowLongPress
,
overlayColor:
overlayColor
,
mouseCursor:
mouseCursor
,
child:
label
,
);
}
...
...
@@ -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
;
...
...
@@ -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
,
...
...
@@ -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
,
...
...
@@ -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
;
}
...
...
@@ -1138,6 +1190,7 @@ class TableRowInkWell extends InkResponse {
super
.
onLongPress
,
super
.
onHighlightChanged
,
super
.
overlayColor
,
super
.
mouseCursor
,
})
:
super
(
containedInkWell:
true
,
highlightShape:
BoxShape
.
rectangle
,
...
...
packages/flutter/lib/src/material/data_table_theme.dart
View file @
f7fb14ec
...
...
@@ -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.'
),
...
...
@@ -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
({
...
...
@@ -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
,
...
...
@@ -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
,
);
}
...
...
@@ -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
,
);
}
...
...
@@ -183,6 +197,8 @@ class DataTableThemeData with Diagnosticable {
columnSpacing
,
dividerThickness
,
checkboxHorizontalMargin
,
headingCellCursor
,
dataRowCursor
,
);
@override
...
...
@@ -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
...
...
@@ -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
));
}
}
...
...
packages/flutter/test/material/data_table_test.dart
View file @
f7fb14ec
...
...
@@ -9,6 +9,7 @@ import 'dart:math' as math;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:vector_math/vector_math_64.dart'
show
Matrix3
;
...
...
@@ -2072,4 +2073,169 @@ void main() {
expect
(()
=>
createDataTable
(
dataRowHeight:
1.0
,
dataRowMinHeight:
2.0
),
throwsA
(
predicate
((
AssertionError
e
)
=>
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
);
});
}
packages/flutter/test/material/data_table_theme_test.dart
View file @
f7fb14ec
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment