diff --git a/packages/two_dimensional_scrollables/CHANGELOG.md b/packages/two_dimensional_scrollables/CHANGELOG.md index 570519e6898e..0a76c0b187e3 100644 --- a/packages/two_dimensional_scrollables/CHANGELOG.md +++ b/packages/two_dimensional_scrollables/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.4 + +* Adds TableSpanPadding, TableSpan.padding, and TableSpanDecoration.consumeSpanPadding. + ## 0.0.3 * Fixes paint issue when axes are reversed and TableView has pinned rows and columns. diff --git a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart index b8829f04ccfa..b6e249c2e026 100644 --- a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart @@ -308,6 +308,13 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { ); } + // TODO(Piinks): Pinned rows/cols do not account for what is visible on the + // screen. Ostensibly, we would not want to have pinned rows/columns that + // extend beyond the viewport, we would never see them as they would never + // scroll into view. So this currently implementation is fairly assuming + // we will never have rows/cols that are outside of the viewport. We should + // maybe add an assertion for this during layout. + // https://github.com/flutter/flutter/issues/136833 int? get _lastPinnedRow => delegate.pinnedRowCount > 0 ? delegate.pinnedRowCount - 1 : null; int? get _lastPinnedColumn => @@ -667,12 +674,17 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { }) { // TODO(Piinks): Assert here or somewhere else merged cells cannot span // pinned and unpinned cells (for merged cell follow-up), https://github.com/flutter/flutter/issues/131224 + _Span colSpan, rowSpan; double yPaintOffset = -offset.dy; for (int row = start.row; row <= end.row; row += 1) { double xPaintOffset = -offset.dx; - final double rowHeight = _rowMetrics[row]!.extent; + rowSpan = _rowMetrics[row]!; + final double rowHeight = rowSpan.extent; + yPaintOffset += rowSpan.configuration.padding.leading; for (int column = start.column; column <= end.column; column += 1) { - final double columnWidth = _columnMetrics[column]!.extent; + colSpan = _columnMetrics[column]!; + final double columnWidth = colSpan.extent; + xPaintOffset += colSpan.configuration.padding.leading; final TableVicinity vicinity = TableVicinity(column: column, row: row); // TODO(Piinks): Add back merged cells, https://github.com/flutter/flutter/issues/131224 @@ -689,9 +701,11 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { cell.layout(cellConstraints); cellParentData.layoutOffset = Offset(xPaintOffset, yPaintOffset); } - xPaintOffset += columnWidth; + xPaintOffset += columnWidth + + _columnMetrics[column]!.configuration.padding.trailing; } - yPaintOffset += rowHeight; + yPaintOffset += + rowHeight + _rowMetrics[row]!.configuration.padding.trailing; } } @@ -836,10 +850,11 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { final LinkedHashMap backgroundColumns = LinkedHashMap(); + final TableSpan rowSpan = _rowMetrics[leading.row]!.configuration; for (int column = leading.column; column <= trailing.column; column++) { - final _Span span = _columnMetrics[column]!; - if (span.configuration.backgroundDecoration != null || - span.configuration.foregroundDecoration != null) { + final TableSpan columnSpan = _columnMetrics[column]!.configuration; + if (columnSpan.backgroundDecoration != null || + columnSpan.foregroundDecoration != null) { final RenderBox leadingCell = getChildFor( TableVicinity(column: column, row: leading.row), )!; @@ -847,18 +862,33 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { TableVicinity(column: column, row: trailing.row), )!; - final Rect rect = Rect.fromPoints( - parentDataOf(leadingCell).paintOffset! + offset, - parentDataOf(trailingCell).paintOffset! + - Offset(trailingCell.size.width, trailingCell.size.height) + - offset, - ); + Rect getColumnRect(bool consumePadding) { + return Rect.fromPoints( + parentDataOf(leadingCell).paintOffset! + + offset - + Offset( + consumePadding ? columnSpan.padding.leading : 0.0, + rowSpan.padding.leading, + ), + parentDataOf(trailingCell).paintOffset! + + offset + + Offset(trailingCell.size.width, trailingCell.size.height) + + Offset( + consumePadding ? columnSpan.padding.trailing : 0.0, + rowSpan.padding.trailing, + ), + ); + } - if (span.configuration.backgroundDecoration != null) { - backgroundColumns[rect] = span.configuration.backgroundDecoration!; + if (columnSpan.backgroundDecoration != null) { + final Rect rect = getColumnRect( + columnSpan.backgroundDecoration!.consumeSpanPadding); + backgroundColumns[rect] = columnSpan.backgroundDecoration!; } - if (span.configuration.foregroundDecoration != null) { - foregroundColumns[rect] = span.configuration.foregroundDecoration!; + if (columnSpan.foregroundDecoration != null) { + final Rect rect = getColumnRect( + columnSpan.foregroundDecoration!.consumeSpanPadding); + foregroundColumns[rect] = columnSpan.foregroundDecoration!; } } } @@ -869,10 +899,11 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { final LinkedHashMap backgroundRows = LinkedHashMap(); + final TableSpan columnSpan = _columnMetrics[leading.column]!.configuration; for (int row = leading.row; row <= trailing.row; row++) { - final _Span span = _rowMetrics[row]!; - if (span.configuration.backgroundDecoration != null || - span.configuration.foregroundDecoration != null) { + final TableSpan rowSpan = _rowMetrics[row]!.configuration; + if (rowSpan.backgroundDecoration != null || + rowSpan.foregroundDecoration != null) { final RenderBox leadingCell = getChildFor( TableVicinity(column: leading.column, row: row), )!; @@ -880,17 +911,33 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { TableVicinity(column: trailing.column, row: row), )!; - final Rect rect = Rect.fromPoints( - parentDataOf(leadingCell).paintOffset! + offset, - parentDataOf(trailingCell).paintOffset! + - Offset(trailingCell.size.width, trailingCell.size.height) + - offset, - ); - if (span.configuration.backgroundDecoration != null) { - backgroundRows[rect] = span.configuration.backgroundDecoration!; + Rect getRowRect(bool consumePadding) { + return Rect.fromPoints( + parentDataOf(leadingCell).paintOffset! + + offset - + Offset( + columnSpan.padding.leading, + consumePadding ? rowSpan.padding.leading : 0.0, + ), + parentDataOf(trailingCell).paintOffset! + + offset + + Offset(trailingCell.size.width, trailingCell.size.height) + + Offset( + columnSpan.padding.leading, + consumePadding ? rowSpan.padding.trailing : 0.0, + ), + ); + } + + if (rowSpan.backgroundDecoration != null) { + final Rect rect = + getRowRect(rowSpan.backgroundDecoration!.consumeSpanPadding); + backgroundRows[rect] = rowSpan.backgroundDecoration!; } - if (span.configuration.foregroundDecoration != null) { - foregroundRows[rect] = span.configuration.foregroundDecoration!; + if (rowSpan.foregroundDecoration != null) { + final Rect rect = + getRowRect(rowSpan.foregroundDecoration!.consumeSpanPadding); + foregroundRows[rect] = rowSpan.foregroundDecoration!; } } } @@ -1028,7 +1075,12 @@ class _Span bool get isPinned => _isPinned; late bool _isPinned; - double get trailingOffset => leadingOffset + extent; + double get trailingOffset { + return leadingOffset + + extent + + configuration.padding.leading + + configuration.padding.trailing; + } // ---- Span Management ---- diff --git a/packages/two_dimensional_scrollables/lib/src/table_view/table_span.dart b/packages/two_dimensional_scrollables/lib/src/table_view/table_span.dart index fbfd9155a15a..fb6fb5c1f912 100644 --- a/packages/two_dimensional_scrollables/lib/src/table_view/table_span.dart +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table_span.dart @@ -10,6 +10,42 @@ import 'package:flutter/widgets.dart'; import 'table.dart'; +/// Defines the leading and trailing padding values of a [TableSpan]. +class TableSpanPadding { + /// Creates a padding configuration for a [TableSpan]. + const TableSpanPadding({ + this.leading = 0.0, + this.trailing = 0.0, + }); + + /// Creates padding where both the [leading] and [trailing] are `value`. + const TableSpanPadding.all(double value) + : leading = value, + trailing = value; + + /// The leading amount of pixels to pad a [TableSpan] by. + /// + /// If the [TableSpan] is a row and the vertical [Axis] is not reversed, this + /// offset will be applied above the row. If the vertical [Axis] is reversed, + /// this will be applied below the row. + /// + /// If the [TableSpan] is a column and the horizontal [Axis] is not reversed, + /// this offset will be applied to the left the column. If the horizontal + /// [Axis] is reversed, this will be applied to the right of the column. + final double leading; + + /// The trailing amount of pixels to pad a [TableSpan] by. + /// + /// If the [TableSpan] is a row and the vertical [Axis] is not reversed, this + /// offset will be applied below the row. If the vertical [Axis] is reversed, + /// this will be applied above the row. + /// + /// If the [TableSpan] is a column and the horizontal [Axis] is not reversed, + /// this offset will be applied to the right the column. If the horizontal + /// [Axis] is reversed, this will be applied to the left of the column. + final double trailing; +} + /// Defines the extent, visual appearance, and gesture handling of a row or /// column in a [TableView]. /// @@ -20,13 +56,14 @@ class TableSpan { /// The [extent] argument must be provided. const TableSpan({ required this.extent, + TableSpanPadding? padding, this.recognizerFactories = const {}, this.onEnter, this.onExit, this.cursor = MouseCursor.defer, this.backgroundDecoration, this.foregroundDecoration, - }); + }) : padding = padding ?? const TableSpanPadding(); /// Defines the extent of the span. /// @@ -34,6 +71,11 @@ class TableSpan { /// represents a column, this is the width of the column. final TableSpanExtent extent; + /// Defines the leading and or trailing extent to pad the row or column by. + /// + /// Defaults to no padding. + final TableSpanPadding padding; + /// Factory for creating [GestureRecognizer]s that want to compete for /// gestures within the [extent] of the span. /// @@ -251,7 +293,11 @@ class MinTableSpanExtent extends CombiningTableSpanExtent { /// A decoration for a [TableSpan]. class TableSpanDecoration { /// Creates a [TableSpanDecoration]. - const TableSpanDecoration({this.border, this.color}); + const TableSpanDecoration({ + this.border, + this.color, + this.consumeSpanPadding = true, + }); /// The border drawn around the span. final TableSpanBorder? border; @@ -259,6 +305,51 @@ class TableSpanDecoration { /// The color to fill the bounds of the span with. final Color? color; + /// Whether or not the decoration should extend to fill the space created by + /// the [TableSpanPadding]. + /// + /// Defaults to true, meaning if a [TableSpan] is a row, the decoration will + /// apply to the full [TableSpanExtent], including the + /// [TableSpanPadding.leading] and [TableSpanPadding.trailing] for the row. + /// This same row decoration will consume any padding from the column spans so + /// as to decorate the row as one continuous span. + /// + /// {@tool snippet} + /// This example illustrates how [consumeSpanPadding] affects + /// [TableSpanDecoration.color]. By default, the color of the decoration + /// consumes the padding, coloring the row fully by including the padding + /// around the row. When [consumeSpanPadding] is false, the padded area of + /// the row is not decorated. + /// + /// ```dart + /// TableView.builder( + /// rowCount: 4, + /// columnCount: 4, + /// columnBuilder: (int index) => TableSpan( + /// extent: const FixedTableSpanExtent(150.0), + /// padding: const TableSpanPadding(trailing: 10), + /// ), + /// rowBuilder: (int index) => TableSpan( + /// extent: const FixedTableSpanExtent(150.0), + /// padding: TableSpanPadding(leading: 10, trailing: 10), + /// backgroundDecoration: TableSpanDecoration( + /// color: index.isOdd ? Colors.blue : Colors.green, + /// // The background color will not be applied to the padded area. + /// consumeSpanPadding: false, + /// ), + /// ), + /// cellBuilder: (_, TableVicinity vicinity) { + /// return Container( + /// height: 150, + /// width: 150, + /// child: const Center(child: FlutterLogo()), + /// ); + /// }, + /// ); + /// ``` + /// {@end-tool} + final bool consumeSpanPadding; + /// Called to draw the decoration around a span. /// /// The provided [TableSpanDecorationPaintDetails] describes the bounds and diff --git a/packages/two_dimensional_scrollables/pubspec.yaml b/packages/two_dimensional_scrollables/pubspec.yaml index bd30a0106dbc..9a392a8bf4b2 100644 --- a/packages/two_dimensional_scrollables/pubspec.yaml +++ b/packages/two_dimensional_scrollables/pubspec.yaml @@ -1,6 +1,6 @@ name: two_dimensional_scrollables description: Widgets that scroll using the two dimensional scrolling foundation. -version: 0.0.3 +version: 0.0.4 repository: https://github.com/flutter/packages/tree/main/packages/two_dimensional_scrollables issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+two_dimensional_scrollables%22+ diff --git a/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png b/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png index baf2cd358cf6..44cb6497b63f 100644 Binary files a/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png and b/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png differ diff --git a/packages/two_dimensional_scrollables/test/table_view/table_test.dart b/packages/two_dimensional_scrollables/test/table_view/table_test.dart index 75cdfb0b9e3d..5dd5da3eb5bc 100644 --- a/packages/two_dimensional_scrollables/test/table_view/table_test.dart +++ b/packages/two_dimensional_scrollables/test/table_view/table_test.dart @@ -329,6 +329,127 @@ void main() { expect(parentData.isVisible, isFalse); }); + testWidgets('TableSpanPadding', (WidgetTester tester) async { + final Map childKeys = + {}; + const TableSpan columnSpan = TableSpan( + extent: FixedTableSpanExtent(200), + padding: TableSpanPadding( + leading: 10.0, + trailing: 20.0, + ), + ); + const TableSpan rowSpan = TableSpan( + extent: FixedTableSpanExtent(200), + padding: TableSpanPadding( + leading: 30.0, + trailing: 40.0, + ), + ); + TableView tableView = TableView.builder( + rowCount: 2, + columnCount: 2, + columnBuilder: (_) => columnSpan, + rowBuilder: (_) => rowSpan, + cellBuilder: (_, TableVicinity vicinity) { + childKeys[vicinity] = childKeys[vicinity] ?? UniqueKey(); + return SizedBox.square(key: childKeys[vicinity], dimension: 200); + }, + ); + TableViewParentData parentDataOf(RenderBox child) { + return child.parentData! as TableViewParentData; + } + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + RenderTwoDimensionalViewport viewport = getViewport( + tester, + childKeys.values.first, + ); + // first child + TableVicinity vicinity = const TableVicinity(column: 0, row: 0); + TableViewParentData parentData = parentDataOf( + viewport.firstChild!, + ); + expect(parentData.vicinity, vicinity); + expect( + parentData.layoutOffset, + const Offset( + 10.0, // Leading 10 pixels before first column + 30.0, // leading 30 pixels before first row + ), + ); + // after first child + vicinity = const TableVicinity(column: 1, row: 0); + + parentData = parentDataOf( + viewport.childAfter(viewport.firstChild!)!, + ); + expect(parentData.vicinity, vicinity); + expect( + parentData.layoutOffset, + const Offset( + 240, // 10 leading + 200 first column + 20 trailing + 10 leading + 30.0, // leading 30 pixels before first row + ), + ); + + // last child + vicinity = const TableVicinity(column: 1, row: 1); + parentData = parentDataOf(viewport.lastChild!); + expect(parentData.vicinity, vicinity); + expect( + parentData.layoutOffset, + const Offset( + 240.0, // 10 leading + 200 first column + 20 trailing + 10 leading + 300.0, // 30 leading + 200 first row + 40 trailing + 30 leading + ), + ); + + // reverse + tableView = TableView.builder( + rowCount: 2, + columnCount: 2, + verticalDetails: const ScrollableDetails.vertical(reverse: true), + horizontalDetails: const ScrollableDetails.horizontal(reverse: true), + columnBuilder: (_) => columnSpan, + rowBuilder: (_) => rowSpan, + cellBuilder: (_, TableVicinity vicinity) { + childKeys[vicinity] = childKeys[vicinity] ?? UniqueKey(); + return SizedBox.square(key: childKeys[vicinity], dimension: 200); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + viewport = getViewport( + tester, + childKeys.values.first, + ); + // first child + vicinity = const TableVicinity(column: 0, row: 0); + parentData = parentDataOf( + viewport.firstChild!, + ); + expect(parentData.vicinity, vicinity); + // layoutOffset is later corrected for reverse in the paintOffset + expect(parentData.paintOffset, const Offset(590.0, 370.0)); + // after first child + vicinity = const TableVicinity(column: 1, row: 0); + + parentData = parentDataOf( + viewport.childAfter(viewport.firstChild!)!, + ); + expect(parentData.vicinity, vicinity); + expect(parentData.paintOffset, const Offset(360.0, 370.0)); + + // last child + vicinity = const TableVicinity(column: 1, row: 1); + parentData = parentDataOf(viewport.lastChild!); + expect(parentData.vicinity, vicinity); + expect(parentData.paintOffset, const Offset(360.0, 100.0)); + }); + testWidgets('TableSpan gesture hit testing', (WidgetTester tester) async { int tapCounter = 0; // Rows @@ -568,6 +689,190 @@ void main() { expect(rowExtent.delegate.viewportExtent, 600.0); }); + testWidgets('First row/column layout based on padding', + (WidgetTester tester) async { + // Huge padding, first span layout + // Column-wise + TableView tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + columnBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(100), + // This padding is so high, only the first column should be laid out. + padding: TableSpanPadding(leading: 2000), + ), + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + // All of these children are so offset by the column padding that they are + // outside of the viewport and cache extent, so all but the very + // first column is laid out. This is so that the ability to scroll the + // table through means such as focus traversal are still accessible. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 1 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 1'), findsNothing); + expect(find.text('Row: 1 Column: 1'), findsNothing); + expect(find.text('Row: 0 Column: 2'), findsNothing); + expect(find.text('Row: 1 Column: 2'), findsNothing); + + // Row-wise + tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + // This padding is so high, no children should be laid out. + rowBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(100), + padding: TableSpanPadding(leading: 2000), + ), + columnBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + // All of these children are so offset by the row padding that they are + // outside of the viewport and cache extent, so all but the very + // first row is laid out. This is so that the ability to scroll the + // table through means such as focus traversal are still accessible. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 1'), findsOneWidget); + expect(find.text('Row: 1 Column: 0'), findsNothing); + expect(find.text('Row: 1 Column: 1'), findsNothing); + expect(find.text('Row: 2 Column: 0'), findsNothing); + expect(find.text('Row: 2 Column: 1'), findsNothing); + }); + + testWidgets('lazy layout accounts for gradually accrued padding', + (WidgetTester tester) async { + // Check with gradually accrued paddings + // Column-wise + TableView tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + columnBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(200), + ), + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 200, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + // No padding here, check all lazily laid out columns in one row. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 1'), findsOneWidget); + expect(find.text('Row: 0 Column: 2'), findsOneWidget); + expect(find.text('Row: 0 Column: 3'), findsOneWidget); + expect(find.text('Row: 0 Column: 4'), findsOneWidget); + expect(find.text('Row: 0 Column: 5'), findsOneWidget); + expect(find.text('Row: 0 Column: 6'), findsNothing); + + tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + columnBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(200), + padding: TableSpanPadding(trailing: 200), + ), + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 200, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + // Fewer children laid out. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 1'), findsOneWidget); + expect(find.text('Row: 0 Column: 2'), findsOneWidget); + expect(find.text('Row: 0 Column: 3'), findsNothing); + expect(find.text('Row: 0 Column: 4'), findsNothing); + expect(find.text('Row: 0 Column: 5'), findsNothing); + expect(find.text('Row: 0 Column: 6'), findsNothing); + + // Row-wise + tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + rowBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(200), + ), + columnBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 200, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + // No padding here, check all lazily laid out rows in one column. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 1 Column: 0'), findsOneWidget); + expect(find.text('Row: 2 Column: 0'), findsOneWidget); + expect(find.text('Row: 3 Column: 0'), findsOneWidget); + expect(find.text('Row: 4 Column: 0'), findsOneWidget); + expect(find.text('Row: 5 Column: 0'), findsNothing); + + tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + rowBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(200), + padding: TableSpanPadding(trailing: 200), + ), + columnBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 200, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + // Fewer children laid out. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 1 Column: 0'), findsOneWidget); + expect(find.text('Row: 2 Column: 0'), findsOneWidget); + expect(find.text('Row: 3 Column: 0'), findsNothing); + expect(find.text('Row: 4 Column: 0'), findsNothing); + expect(find.text('Row: 5 Column: 0'), findsNothing); + + // Check padding with pinned rows and columns + // TODO(Piinks): Pinned rows/columns are not lazily laid out, should check + // for assertions in this case. Will add in https://github.com/flutter/flutter/issues/136833 + }); + testWidgets('regular layout - no pinning', (WidgetTester tester) async { final ScrollController verticalController = ScrollController(); final ScrollController horizontalController = ScrollController(); @@ -950,13 +1255,17 @@ void main() { (WidgetTester tester) async { // TODO(Piinks): Rewrite this to remove golden files from this repo when // mock_canvas is public - https://github.com/flutter/flutter/pull/131631 - // foreground, background, and precedence per mainAxis + // * foreground, background, and precedence per mainAxis + // * Break out a separate test for padding decorations to validate paint + // rect calls TableView tableView = TableView.builder( rowCount: 2, columnCount: 2, columnBuilder: (int index) => TableSpan( extent: const FixedTableSpanExtent(200.0), + padding: index == 0 ? const TableSpanPadding(trailing: 10) : null, foregroundDecoration: const TableSpanDecoration( + consumeSpanPadding: false, border: TableSpanBorder( trailing: BorderSide( color: Colors.orange, @@ -965,12 +1274,15 @@ void main() { ), ), backgroundDecoration: TableSpanDecoration( + // consumePadding true by default color: index.isEven ? Colors.red : null, ), ), rowBuilder: (int index) => TableSpan( extent: const FixedTableSpanExtent(200.0), + padding: index == 1 ? const TableSpanPadding(leading: 10) : null, foregroundDecoration: const TableSpanDecoration( + // consumePadding true by default border: TableSpanBorder( leading: BorderSide( color: Colors.green, @@ -980,12 +1292,15 @@ void main() { ), backgroundDecoration: TableSpanDecoration( color: index.isOdd ? Colors.blue : null, + consumeSpanPadding: false, ), ), cellBuilder: (_, TableVicinity vicinity) { - return const SizedBox.square( - dimension: 200, - child: Center(child: FlutterLogo()), + return Container( + height: 200, + width: 200, + color: Colors.grey.withOpacity(0.5), + child: const Center(child: FlutterLogo()), ); }, ); @@ -1052,7 +1367,7 @@ void main() { (WidgetTester tester) async { // TODO(Piinks): Rewrite this to remove golden files from this repo when // mock_canvas is public - https://github.com/flutter/flutter/pull/131631 - // foreground, background, and precedence per mainAxis + // * foreground, background, and precedence per mainAxis final TableView tableView = TableView.builder( verticalDetails: const ScrollableDetails.vertical(reverse: true), horizontalDetails: const ScrollableDetails.horizontal(reverse: true),