blob: 29d8a987d39878051b3dbdc11a4db3f92cdebec7 [file]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of 'table.dart';
/// If a [ColumnData] implements this interface, it can override how that cell
/// is rendered.
abstract class ColumnRenderer<T> {
/// Render the given [data] to a [Widget].
///
/// This method can return `null` to indicate that the default rendering
/// should be used instead.
/// `isRowHovered` is only used when `enableHoverHandling` is `true` on the table
/// that this column belongs to.
Widget? build(
BuildContext context,
T data, {
bool isRowSelected = false,
bool isRowHovered = false,
VoidCallback? onPressed,
});
}
/// If a [ColumnData] implements this interface, it can override how that column
/// header is rendered.
abstract class ColumnHeaderRenderer<T> {
/// Render the column header to a [Widget].
///
/// This method can return `null` to indicate that the default rendering
/// should be used instead.
Widget? buildHeader(
BuildContext context,
Widget Function() defaultHeaderRenderer,
);
}
class _ColumnHeader<T> extends StatelessWidget {
const _ColumnHeader({
super.key,
required this.column,
required this.isSortColumn,
required this.sortDirection,
required this.onSortChanged,
this.secondarySortColumn,
});
final ColumnData<T> column;
final ColumnData<T>? secondarySortColumn;
final bool isSortColumn;
final SortDirection sortDirection;
final void Function(
ColumnData<T> column,
SortDirection direction, {
ColumnData<T>? secondarySortColumn,
})?
onSortChanged;
@override
Widget build(BuildContext context) {
final title = Text(
column.title,
overflow: TextOverflow.ellipsis,
textAlign: column.headerAlignment,
);
final headerContent = Row(
mainAxisAlignment: column.mainAxisAlignment,
children: [
if (isSortColumn && column.supportsSorting) ...[
Icon(
sortDirection == SortDirection.ascending
? Icons.expand_less
: Icons.expand_more,
size: defaultIconSize,
),
const SizedBox(width: densePadding),
],
Expanded(
child:
column.titleTooltip != null
? DevToolsTooltip(
message: column.titleTooltip,
padding: const EdgeInsets.all(denseSpacing),
child: title,
)
: title,
),
],
);
return column.includeHeader
? InkWell(
canRequestFocus: false,
onTap:
column.supportsSorting
? () => _handleSortChange(
column,
secondarySortColumn: secondarySortColumn,
)
: null,
child: headerContent,
)
: headerContent;
}
void _handleSortChange(
ColumnData<T> columnData, {
ColumnData<T>? secondarySortColumn,
}) {
SortDirection direction;
if (isSortColumn) {
direction = sortDirection.reverse();
} else if (columnData.numeric) {
direction = SortDirection.descending;
} else {
direction = SortDirection.ascending;
}
onSortChanged?.call(
columnData,
direction,
secondarySortColumn: secondarySortColumn,
);
}
}
class _ColumnGroupHeaderRow extends StatelessWidget {
const _ColumnGroupHeaderRow({
required this.groups,
required this.columnWidths,
required this.scrollController,
});
final List<ColumnGroup> groups;
final List<double> columnWidths;
final ScrollController scrollController;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: defaultSpacing),
decoration: BoxDecoration(
border: Border(bottom: defaultBorderSide(Theme.of(context))),
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
controller: scrollController,
itemCount: groups.length + groups.numSpacers,
itemBuilder: (context, int i) {
if (i % 2 == 1) {
return const _ColumnGroupSpacer();
}
final group = groups[i ~/ 2];
final groupRange = group.range;
double groupWidth = 0.0;
for (int j = groupRange.begin as int; j < groupRange.end; j++) {
final columnWidth = columnWidths[j];
groupWidth += columnWidth;
if (j < groupRange.end - 1) {
groupWidth += columnSpacing;
}
}
return Container(
alignment: Alignment.center,
width: groupWidth,
child: group.title,
);
},
),
);
}
}
class _ColumnGroupSpacer extends StatelessWidget {
const _ColumnGroupSpacer();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: (columnGroupSpacingWithPadding - columnGroupSpacing) / 2,
),
child: Container(
width: columnGroupSpacing,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.black, Theme.of(context).focusColor, Colors.black],
),
),
),
);
}
}