From d357abdc418f968d30a87c2a0eacc20dd699f602 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Fri, 29 Sep 2023 15:55:44 -0500 Subject: [PATCH 1/6] Add TableSpanPadding --- .../lib/src/table_view/table.dart | 10 +- .../lib/src/table_view/table_span.dart | 37 ++++++ .../test/table_view/table_test.dart | 118 ++++++++++++++++++ 3 files changed, 162 insertions(+), 3 deletions(-) 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 b8829f04ccf..876e2680c7f 100644 --- a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart @@ -667,12 +667,14 @@ 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 - double yPaintOffset = -offset.dy; + late double yPaintOffset = -offset.dy; for (int row = start.row; row <= end.row; row += 1) { double xPaintOffset = -offset.dx; final double rowHeight = _rowMetrics[row]!.extent; + yPaintOffset += _rowMetrics[row]!.configuration.padding.leading; for (int column = start.column; column <= end.column; column += 1) { final double columnWidth = _columnMetrics[column]!.extent; + xPaintOffset += _columnMetrics[column]!.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 +691,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; } } 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 fbfd9155a15..18995996103 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,37 @@ 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, + }); + + /// 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,6 +51,7 @@ class TableSpan { /// The [extent] argument must be provided. const TableSpan({ required this.extent, + this.padding = const TableSpanPadding(), this.recognizerFactories = const {}, this.onEnter, this.onExit, @@ -34,6 +66,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. /// 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 75cdfb0b9e3..db57ceb09d2 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,124 @@ 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 From c62a209cbc07b11e578225251d3e6d85317a5e29 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Tue, 10 Oct 2023 13:52:19 -0500 Subject: [PATCH 2/6] ++ --- .../two_dimensional_scrollables/lib/src/table_view/table.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 876e2680c7f..686457f30a7 100644 --- a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart @@ -667,7 +667,7 @@ 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 - late double yPaintOffset = -offset.dy; + double yPaintOffset = -offset.dy; for (int row = start.row; row <= end.row; row += 1) { double xPaintOffset = -offset.dx; final double rowHeight = _rowMetrics[row]!.extent; From 0c0904946ba320ac75a1a10fe37ad5cd0d03c4e4 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Fri, 13 Oct 2023 18:21:57 -0500 Subject: [PATCH 3/6] More edge cases --- .../lib/src/table_view/table.dart | 77 +++++++++++++------ .../test/table_view/table_test.dart | 62 +++++++++++++++ 2 files changed, 116 insertions(+), 23 deletions(-) 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 686457f30a7..b0edebc39e8 100644 --- a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart @@ -667,16 +667,20 @@ 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; - yPaintOffset += _rowMetrics[row]!.configuration.padding.leading; + 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; - xPaintOffset += _columnMetrics[column]!.configuration.padding.leading; + colSpan = _columnMetrics[column]!; + final double columnWidth = colSpan.extent; + xPaintOffset += colSpan.configuration.padding.leading; final TableVicinity vicinity = TableVicinity(column: column, row: row); + print(vicinity); // TODO(Piinks): Add back merged cells, https://github.com/flutter/flutter/issues/131224 final RenderBox? cell = buildOrObtainChildFor(vicinity); @@ -840,10 +844,11 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { final LinkedHashMap backgroundColumns = LinkedHashMap(); + final _Span rowSpan = _rowMetrics[leading.row]!; for (int column = leading.column; column <= trailing.column; column++) { - final _Span span = _columnMetrics[column]!; - if (span.configuration.backgroundDecoration != null || - span.configuration.foregroundDecoration != null) { + final _Span columnSpan = _columnMetrics[column]!; + if (columnSpan.configuration.backgroundDecoration != null || + columnSpan.configuration.foregroundDecoration != null) { final RenderBox leadingCell = getChildFor( TableVicinity(column: column, row: leading.row), )!; @@ -852,17 +857,28 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { )!; final Rect rect = Rect.fromPoints( - parentDataOf(leadingCell).paintOffset! + offset, + parentDataOf(leadingCell).paintOffset! + + offset - + Offset( + columnSpan.configuration.padding.leading, + rowSpan.configuration.padding.leading, + ), parentDataOf(trailingCell).paintOffset! + Offset(trailingCell.size.width, trailingCell.size.height) + - offset, + offset + + Offset( + columnSpan.configuration.padding.trailing, + rowSpan.configuration.padding.trailing, + ), ); - if (span.configuration.backgroundDecoration != null) { - backgroundColumns[rect] = span.configuration.backgroundDecoration!; + if (columnSpan.configuration.backgroundDecoration != null) { + backgroundColumns[rect] = + columnSpan.configuration.backgroundDecoration!; } - if (span.configuration.foregroundDecoration != null) { - foregroundColumns[rect] = span.configuration.foregroundDecoration!; + if (columnSpan.configuration.foregroundDecoration != null) { + foregroundColumns[rect] = + columnSpan.configuration.foregroundDecoration!; } } } @@ -873,10 +889,11 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { final LinkedHashMap backgroundRows = LinkedHashMap(); + final _Span columnSpan = _columnMetrics[leading.column]!; for (int row = leading.row; row <= trailing.row; row++) { - final _Span span = _rowMetrics[row]!; - if (span.configuration.backgroundDecoration != null || - span.configuration.foregroundDecoration != null) { + final _Span rowSpan = _rowMetrics[row]!; + if (rowSpan.configuration.backgroundDecoration != null || + rowSpan.configuration.foregroundDecoration != null) { final RenderBox leadingCell = getChildFor( TableVicinity(column: leading.column, row: row), )!; @@ -885,16 +902,25 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { )!; final Rect rect = Rect.fromPoints( - parentDataOf(leadingCell).paintOffset! + offset, + parentDataOf(leadingCell).paintOffset! + + offset - + Offset( + columnSpan.configuration.padding.leading, + rowSpan.configuration.padding.leading, + ), parentDataOf(trailingCell).paintOffset! + Offset(trailingCell.size.width, trailingCell.size.height) + - offset, + offset + + Offset( + columnSpan.configuration.padding.leading, + rowSpan.configuration.padding.trailing, + ), ); - if (span.configuration.backgroundDecoration != null) { - backgroundRows[rect] = span.configuration.backgroundDecoration!; + if (rowSpan.configuration.backgroundDecoration != null) { + backgroundRows[rect] = rowSpan.configuration.backgroundDecoration!; } - if (span.configuration.foregroundDecoration != null) { - foregroundRows[rect] = span.configuration.foregroundDecoration!; + if (rowSpan.configuration.foregroundDecoration != null) { + foregroundRows[rect] = rowSpan.configuration.foregroundDecoration!; } } } @@ -1032,7 +1058,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/test/table_view/table_test.dart b/packages/two_dimensional_scrollables/test/table_view/table_test.dart index db57ceb09d2..b6f1c5b078c 100644 --- a/packages/two_dimensional_scrollables/test/table_view/table_test.dart +++ b/packages/two_dimensional_scrollables/test/table_view/table_test.dart @@ -686,6 +686,68 @@ void main() { expect(rowExtent.delegate.viewportExtent, 600.0); }); + testWidgets('Does not lay out extra children on account of padding', (WidgetTester tester) async { + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + // Large column padding + TableView tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + // This padding is so high, no children should be laid out. + columnBuilder: (_) => const TableSpan( + extent: FixedTableSpanExtent(100), + padding: TableSpanPadding(leading: 1051), // 800 viewport width, 250 cacheExtent. + ), + 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 padding they are outside of + // the viewport and cache extent + expect(find.text('Row: 0 Column: 0'), findsNothing); + expect(find.text('Row: 1 Column: 0'), findsNothing); + expect(find.text('Row: 0 Column: 1'), findsNothing); + expect(find.text('Row: 1 Column: 1'), findsNothing); + + // Large row padding + 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: 851), // 600 viewport height, + 250 cacheExtent + ), + 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 padding they are outside of + // the viewport and cache extent + expect(find.text('Row: 0 Column: 0'), findsNothing); + expect(find.text('Row: 1 Column: 0'), findsNothing); + expect(find.text('Row: 0 Column: 1'), findsNothing); + expect(find.text('Row: 1 Column: 1'), findsNothing); + + // Check with pinned rows and columns + + // Check with eventually accrued paddings + }); + testWidgets('regular layout - no pinning', (WidgetTester tester) async { final ScrollController verticalController = ScrollController(); final ScrollController horizontalController = ScrollController(); From d8040f2f9c20273ce0873495c30ee35ca14e3e9a Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Wed, 18 Oct 2023 13:48:33 -0500 Subject: [PATCH 4/6] ++ --- .../lib/src/table_view/table.dart | 103 ++++++++++-------- 1 file changed, 56 insertions(+), 47 deletions(-) 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 b0edebc39e8..316e8522185 100644 --- a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart @@ -844,41 +844,45 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { final LinkedHashMap backgroundColumns = LinkedHashMap(); - final _Span rowSpan = _rowMetrics[leading.row]!; + final TableSpan rowSpan = _rowMetrics[leading.row]!.configuration; for (int column = leading.column; column <= trailing.column; column++) { - final _Span columnSpan = _columnMetrics[column]!; - if (columnSpan.configuration.backgroundDecoration != null || - columnSpan.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), )!; final RenderBox trailingCell = getChildFor( TableVicinity(column: column, row: trailing.row), )!; + + 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(trailingCell.size.width, trailingCell.size.height) + + offset + + Offset( + consumePadding ? columnSpan.padding.trailing : 0.0, + rowSpan.padding.trailing, + ), + ); + } - final Rect rect = Rect.fromPoints( - parentDataOf(leadingCell).paintOffset! + - offset - - Offset( - columnSpan.configuration.padding.leading, - rowSpan.configuration.padding.leading, - ), - parentDataOf(trailingCell).paintOffset! + - Offset(trailingCell.size.width, trailingCell.size.height) + - offset + - Offset( - columnSpan.configuration.padding.trailing, - rowSpan.configuration.padding.trailing, - ), - ); - - if (columnSpan.configuration.backgroundDecoration != null) { + if (columnSpan.backgroundDecoration != null) { + final Rect rect = getColumnRect(columnSpan.backgroundDecoration!.consumeSpanPadding); backgroundColumns[rect] = - columnSpan.configuration.backgroundDecoration!; + columnSpan.backgroundDecoration!; } - if (columnSpan.configuration.foregroundDecoration != null) { + if (columnSpan.foregroundDecoration != null) { + final Rect rect = getColumnRect(columnSpan.foregroundDecoration!.consumeSpanPadding); foregroundColumns[rect] = - columnSpan.configuration.foregroundDecoration!; + columnSpan.foregroundDecoration!; } } } @@ -889,11 +893,11 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { final LinkedHashMap backgroundRows = LinkedHashMap(); - final _Span columnSpan = _columnMetrics[leading.column]!; + final TableSpan columnSpan = _columnMetrics[leading.column]!.configuration; for (int row = leading.row; row <= trailing.row; row++) { - final _Span rowSpan = _rowMetrics[row]!; - if (rowSpan.configuration.backgroundDecoration != null || - rowSpan.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), )!; @@ -901,26 +905,31 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { TableVicinity(column: trailing.column, row: row), )!; - final Rect rect = Rect.fromPoints( - parentDataOf(leadingCell).paintOffset! + - offset - - Offset( - columnSpan.configuration.padding.leading, - rowSpan.configuration.padding.leading, - ), - parentDataOf(trailingCell).paintOffset! + - Offset(trailingCell.size.width, trailingCell.size.height) + - offset + - Offset( - columnSpan.configuration.padding.leading, - rowSpan.configuration.padding.trailing, - ), - ); - if (rowSpan.configuration.backgroundDecoration != null) { - backgroundRows[rect] = rowSpan.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(trailingCell.size.width, trailingCell.size.height) + + offset + + 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 (rowSpan.configuration.foregroundDecoration != null) { - foregroundRows[rect] = rowSpan.configuration.foregroundDecoration!; + if (rowSpan.foregroundDecoration != null) { + final Rect rect = getRowRect(rowSpan.foregroundDecoration!.consumeSpanPadding); + foregroundRows[rect] = rowSpan.foregroundDecoration!; } } } From 2245e8c2c6eb1f96ac651bff4ce4cf00510a5222 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Wed, 18 Oct 2023 14:39:46 -0500 Subject: [PATCH 5/6] Capture span decoration consuming padding or not --- .../lib/src/table_view/table.dart | 9 +- .../lib/src/table_view/table_span.dart | 21 +- .../tableSpanDecoration.defaultMainAxis.png | Bin 4914 -> 5003 bytes .../test/table_view/table_test.dart | 184 +++++++++++++++--- 4 files changed, 184 insertions(+), 30 deletions(-) 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 316e8522185..99f169a8e73 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 => @@ -680,7 +687,7 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { xPaintOffset += colSpan.configuration.padding.leading; final TableVicinity vicinity = TableVicinity(column: column, row: row); - print(vicinity); + // TODO(Piinks): Add back merged cells, https://github.com/flutter/flutter/issues/131224 final RenderBox? cell = buildOrObtainChildFor(vicinity); 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 18995996103..a71560b2656 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 @@ -51,14 +51,14 @@ class TableSpan { /// The [extent] argument must be provided. const TableSpan({ required this.extent, - this.padding = const TableSpanPadding(), + 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. /// @@ -288,7 +288,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; @@ -296,6 +300,17 @@ 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. + /// // TODO(Piinks): Add an example of this! + 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/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png b/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png index baf2cd358cf6de8fd3a82c2f09b8b32bcd6717af..44cb6497b63fbea7cc46d2de8579b4c4773e93c9 100644 GIT binary patch delta 2536 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 literal 4914 zcmeH~c~lcd9>*sL%ArKG4ns>NN`2i)hJ` zTDFxdpn{+r;SM!G00CL51tf%&BVfT22!t3y2;s_x-FElwKl}Etef#>Hf9B2a&HQG5 z-#I?Nx#NdI?$X_-3jo-4#_RN_0Bo}WK;we;cC{rp@kX4wf+T#3^Z?9WvsraRGr{AG zzqY!NwZFIxz@D))r$6#1=8FX}6+?1Lw^TydeKJm;C7AXJE593F>nUOpgEEi#q-UV< zdBbrhc=#7lArYcw1RC~pF*H|;JM>8;7v1ELH+mE8)&BS|l~OfT4*Tnt zTWDzL3fC3jIvwM4=2K{_PD}A;}n)3@T#QU|^Pum>V z;T-ztzz)O=D zXqy+VuP`h_m?FwnOjA^Oj<^lc+tdB7)0Jb6emq%YHwTVaOkSToQnwcZNiggc120|mxSnqGRDMzF?XXuqJA>gGp1_bJ^O zbdf`9%}m)9XoH;*c&q9#tT?Xr1wThVx_`IY;Sd_4<2<}V{Pm_0{~^we@>22EXNDGJ zZ3;`R{hXgpN=`O3TMHxabymj!8K^CqnzFXHtxSVq{3PD9=2G@QR^fo`>~8+9Nf=1j z|DB{ z9-qae)c$(K`g@LPp$TVUERbP{%&ERge0WVVKu=@^i0O2cuepoqKt8-u_z1)Jw5~h~ zQ_5A=d2Ty@uiklHlk4Kw(Q5Ak1Nr(#ENo60!+bGu!*OK)07u>vEH=~+%fwi^WmoGz z?~Zlj2o=x_dRbX-iQr?SRw=5*!VQE+f2rMawi5HStRVRkE^K7kqCf58s^Wc8V+A_t zv(W?qN_%y^UNFhxr|eYedM-!N*1i-a%15?_xswT4O3KLla)hMI##dW>P01dYIDSm% zY+tgOAT6fF*lx@(7DUdBvuE4kBl%EplAuwn7g`%n5VR6g1( zcg~(*GD#c)$+a=YS3JiE_33+1$4HVx2l=HxU5$6X%=fJ-g$G};_n_wihXP~dM^l5m(_6rL zbCS71Py4>d1rZg3tHdxh)h~qW1sa_?M-G)OB?V{evOCl%LF$C@k5Da;|0l?R)$i~> zfM8QssIRu5j?wgb@w?d)5&Xe#Ie~TI4c@n}CX2WZ1X*p`&CsU7B1~nJ$)18%t%({FrsqH2vxg=SdE>?0d|2&2cI!o#-02S(9ZQz>)(WvT`29B z2nL(-(#!NiW{9$$fCjk|#tXp( z@|Fu#Nu&-nMm?$c>su&ZGwQ3_bcKuNA3^zF|G5cQo%H1{RDr*2ZHLJ8;8!})GmH&` zft@Gh(|h}dzQ2btOL1dyYRg!yoD#7CgnHfr!S1v&4dS~_uv-d7S*6Qz6thIwKiu22 zxFlm6aeqaI|cq%3QWCJLH2*$%i!5biniuF const TableSpan( - extent: FixedTableSpanExtent(100), - padding: TableSpanPadding(leading: 1051), // 800 viewport width, 250 cacheExtent. + 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) { @@ -709,21 +709,26 @@ void main() { await tester.pumpWidget(MaterialApp(home: tableView)); await tester.pumpAndSettle(); - // All of these children are so offset by the padding they are outside of - // the viewport and cache extent - expect(find.text('Row: 0 Column: 0'), findsNothing); - expect(find.text('Row: 1 Column: 0'), findsNothing); + // 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); - // Large row padding + // Row-wise tableView = TableView.builder( rowCount: 50, columnCount: 50, // This padding is so high, no children should be laid out. - rowBuilder: (_) => const TableSpan( + rowBuilder: (_) => + const TableSpan( extent: FixedTableSpanExtent(100), - padding: TableSpanPadding(leading: 851), // 600 viewport height, + 250 cacheExtent + padding: TableSpanPadding(leading: 2000), ), columnBuilder: (_) => span, cellBuilder: (_, TableVicinity vicinity) { @@ -736,16 +741,133 @@ void main() { await tester.pumpWidget(MaterialApp(home: tableView)); await tester.pumpAndSettle(); - // All of these children are so offset by the padding they are outside of - // the viewport and cache extent - expect(find.text('Row: 0 Column: 0'), findsNothing); + // 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: 0 Column: 1'), 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}'), + ); + }, + ); - // Check with pinned rows and columns + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); - // Check with eventually accrued paddings + // 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 { @@ -1130,13 +1252,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, @@ -1145,12 +1271,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, @@ -1160,12 +1289,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()), ); }, ); @@ -1232,7 +1364,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), From dfbda1c08273f3114f94163320ed4c98f4c9c59a Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Wed, 18 Oct 2023 16:01:54 -0500 Subject: [PATCH 6/6] Nits --- .../two_dimensional_scrollables/CHANGELOG.md | 4 ++ .../lib/src/table_view/table.dart | 53 ++++++++++--------- .../lib/src/table_view/table_span.dart | 41 +++++++++++++- .../two_dimensional_scrollables/pubspec.yaml | 2 +- .../test/table_view/table_test.dart | 45 ++++++++-------- 5 files changed, 96 insertions(+), 49 deletions(-) diff --git a/packages/two_dimensional_scrollables/CHANGELOG.md b/packages/two_dimensional_scrollables/CHANGELOG.md index 570519e6898..0a76c0b187e 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 99f169a8e73..b6e249c2e02 100644 --- a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart @@ -687,7 +687,6 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { 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 final RenderBox? cell = buildOrObtainChildFor(vicinity); @@ -862,34 +861,34 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { final RenderBox trailingCell = getChildFor( TableVicinity(column: column, row: trailing.row), )!; - + 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(trailingCell.size.width, trailingCell.size.height) + - offset + - Offset( - consumePadding ? columnSpan.padding.trailing : 0.0, - rowSpan.padding.trailing, - ), - ); + 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 (columnSpan.backgroundDecoration != null) { - final Rect rect = getColumnRect(columnSpan.backgroundDecoration!.consumeSpanPadding); - backgroundColumns[rect] = - columnSpan.backgroundDecoration!; + final Rect rect = getColumnRect( + columnSpan.backgroundDecoration!.consumeSpanPadding); + backgroundColumns[rect] = columnSpan.backgroundDecoration!; } if (columnSpan.foregroundDecoration != null) { - final Rect rect = getColumnRect(columnSpan.foregroundDecoration!.consumeSpanPadding); - foregroundColumns[rect] = - columnSpan.foregroundDecoration!; + final Rect rect = getColumnRect( + columnSpan.foregroundDecoration!.consumeSpanPadding); + foregroundColumns[rect] = columnSpan.foregroundDecoration!; } } } @@ -921,8 +920,8 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { consumePadding ? rowSpan.padding.leading : 0.0, ), parentDataOf(trailingCell).paintOffset! + - Offset(trailingCell.size.width, trailingCell.size.height) + offset + + Offset(trailingCell.size.width, trailingCell.size.height) + Offset( columnSpan.padding.leading, consumePadding ? rowSpan.padding.trailing : 0.0, @@ -931,11 +930,13 @@ class RenderTableViewport extends RenderTwoDimensionalViewport { } if (rowSpan.backgroundDecoration != null) { - final Rect rect = getRowRect(rowSpan.backgroundDecoration!.consumeSpanPadding); + final Rect rect = + getRowRect(rowSpan.backgroundDecoration!.consumeSpanPadding); backgroundRows[rect] = rowSpan.backgroundDecoration!; } if (rowSpan.foregroundDecoration != null) { - final Rect rect = getRowRect(rowSpan.foregroundDecoration!.consumeSpanPadding); + final Rect rect = + getRowRect(rowSpan.foregroundDecoration!.consumeSpanPadding); foregroundRows[rect] = rowSpan.foregroundDecoration!; } } 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 a71560b2656..fb6fb5c1f91 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 @@ -18,6 +18,11 @@ class TableSpanPadding { 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 @@ -308,7 +313,41 @@ class TableSpanDecoration { /// [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. - /// // TODO(Piinks): Add an example of this! + /// + /// {@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. diff --git a/packages/two_dimensional_scrollables/pubspec.yaml b/packages/two_dimensional_scrollables/pubspec.yaml index bd30a0106db..9a392a8bf4b 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/table_test.dart b/packages/two_dimensional_scrollables/test/table_view/table_test.dart index 74418f19284..5dd5da3eb5b 100644 --- a/packages/two_dimensional_scrollables/test/table_view/table_test.dart +++ b/packages/two_dimensional_scrollables/test/table_view/table_test.dart @@ -373,11 +373,12 @@ void main() { ); 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 - )); + 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); @@ -386,22 +387,24 @@ void main() { ); 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 - )); + 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 - )); + 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( @@ -686,14 +689,14 @@ void main() { expect(rowExtent.delegate.viewportExtent, 600.0); }); - testWidgets('First row/column layout based on padding', (WidgetTester tester) async { + 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( + columnBuilder: (_) => const TableSpan( extent: FixedTableSpanExtent(100), // This padding is so high, only the first column should be laid out. padding: TableSpanPadding(leading: 2000), @@ -725,8 +728,7 @@ void main() { rowCount: 50, columnCount: 50, // This padding is so high, no children should be laid out. - rowBuilder: (_) => - const TableSpan( + rowBuilder: (_) => const TableSpan( extent: FixedTableSpanExtent(100), padding: TableSpanPadding(leading: 2000), ), @@ -753,7 +755,8 @@ void main() { expect(find.text('Row: 2 Column: 1'), findsNothing); }); - testWidgets('lazy layout accounts for gradually accrued padding', (WidgetTester tester) async { + testWidgets('lazy layout accounts for gradually accrued padding', + (WidgetTester tester) async { // Check with gradually accrued paddings // Column-wise TableView tableView = TableView.builder(