Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Rows work with resolution for decorations, example app needs to be re…
…stored/updated
  • Loading branch information
Piinks committed Jan 23, 2024
commit 7245dcd5984ec5508aea9e7a9c13466cec826486
174 changes: 41 additions & 133 deletions packages/two_dimensional_scrollables/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,66 +41,39 @@ class TableExample extends StatefulWidget {
}

class _TableExampleState extends State<TableExample> {
late final ScrollController _verticalController = ScrollController();
int _rowCount = 20;
final Map<TableVicinity, (int, int)> mergedRows= <TableVicinity, (int, int)>{
// TableVicinity in merged cell : (start, span)
TableVicinity.zero : (0, 2),
TableVicinity.zero.copyWith(row: 1): (0, 2),
const TableVicinity(row: 1, column: 1) : (1, 2),
const TableVicinity(row: 2, column: 1) : (1, 2),
const TableVicinity(row: 2, column: 2) : (2, 2),
const TableVicinity(row: 3, column: 2) : (2, 2),
};

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Table Example'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: TableView.builder(
verticalDetails:
ScrollableDetails.vertical(controller: _verticalController),
cellBuilder: _buildCell,
columnCount: 20,
columnBuilder: _buildColumnSpan,
rowCount: _rowCount,
rowBuilder: _buildRowSpan,
),
body: TableView.builder(
cellBuilder: _buildCell,
columnCount: 4,
columnBuilder: _buildColumnSpan,
rowCount: 4,
rowBuilder: _buildRowSpan,
),
persistentFooterButtons: <Widget>[
TextButton(
onPressed: () {
_verticalController.jumpTo(0);
},
child: const Text('Jump to Top'),
),
TextButton(
onPressed: () {
_verticalController
.jumpTo(_verticalController.position.maxScrollExtent);
},
child: const Text('Jump to Bottom'),
),
TextButton(
onPressed: () {
setState(() {
_rowCount += 10;
});
},
child: const Text('Add 10 Rows'),
),
],
);
}

TableViewCell _buildCell(BuildContext context, TableVicinity vicinity) {
final Set<TableVicinity> mergedCells = <TableVicinity>{
TableVicinity.zero.copyWith(row: 1),
TableVicinity.zero.copyWith(row: 2),
};

if (mergedCells.contains(vicinity)) {
print(vicinity);
return const TableViewCell(
rowMergeStart: 1,
rowMergeSpan: 2,
child: Center(
child: Text('Tile c: 0, r: 1'),
if (mergedRows.keys.contains(vicinity)) {
return TableViewCell(
rowMergeStart: mergedRows[vicinity]!.$1,
rowMergeSpan: mergedRows[vicinity]!.$2,
child: const Center(
child: Text('Merged'),
),
);
}
Expand All @@ -112,95 +85,30 @@ class _TableExampleState extends State<TableExample> {
);
}

TableSpan _buildColumnSpan(int index) {
const TableSpanDecoration decoration = TableSpanDecoration(
border: TableSpanBorder(
trailing: BorderSide(),
),
);

switch (index % 5) {
case 0:
return TableSpan(
foregroundDecoration: decoration,
extent: const FixedTableSpanExtent(100),
// onEnter: (_) => print('Entered column $index'),
recognizerFactories: <Type, GestureRecognizerFactory>{
TapGestureRecognizer:
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(),
(TapGestureRecognizer t) =>
t.onTap = () => print('Tap column $index'),
),
},
);
case 1:
return TableSpan(
foregroundDecoration: decoration,
extent: const FractionalTableSpanExtent(0.5),
// onEnter: (_) => print('Entered column $index'),
cursor: SystemMouseCursors.contextMenu,
);
case 2:
return TableSpan(
foregroundDecoration: decoration,
extent: const FixedTableSpanExtent(120),
// onEnter: (_) => print('Entered column $index'),
);
case 3:
return TableSpan(
foregroundDecoration: decoration,
extent: const FixedTableSpanExtent(145),
// onEnter: (_) => print('Entered column $index'),
);
case 4:
return TableSpan(
foregroundDecoration: decoration,
extent: const FixedTableSpanExtent(200),
// onEnter: (_) => print('Entered column $index'),
);
TableSpan _buildRowSpan(int index) {
late final Color color;
switch(index) {
case 1: color = Colors.purple;
case 2: color = Colors.blue;
case 3: color = Colors.green;
default: color = Colors.transparent;
}
throw AssertionError(
'This should be unreachable, as every index is accounted for in the switch clauses.');
}

TableSpan _buildRowSpan(int index) {
final TableSpanDecoration decoration0 = TableSpanDecoration(
color: index.isEven ? Colors.purple[100] : null,
border: const TableSpanBorder(
trailing: BorderSide(
width: 3,
),
return TableSpan(
extent: const FixedTableSpanExtent(100.0),
backgroundDecoration: TableSpanDecoration(
color: color,
border: const TableSpanBorder(leading: BorderSide(), trailing: BorderSide(),),
),
);
}

switch (index % 3) {
case 0:
return TableSpan(
backgroundDecoration: decoration0,
extent: const FixedTableSpanExtent(50),
recognizerFactories: <Type, GestureRecognizerFactory>{
TapGestureRecognizer:
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(),
(TapGestureRecognizer t) =>
t.onTap = () => print('Tap row $index'),
),
},
);
case 1:
return TableSpan(
backgroundDecoration: decoration0,
extent: const FixedTableSpanExtent(65),
cursor: SystemMouseCursors.click,
);
case 2:
return TableSpan(
backgroundDecoration: decoration0,
extent: const FractionalTableSpanExtent(0.15),
);
}
throw AssertionError(
'This should be unreachable, as every index is accounted for in the switch clauses.');
TableSpan _buildColumnSpan(int index) {
return const TableSpan(
extent: FixedTableSpanExtent(100.0),
// foregroundDecoration: TableSpanDecoration(
// border: const TableSpanBorder(leading: BorderSide(), trailing: BorderSide(),),
// ),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {
<TableVicinity, TableVicinity>{};
// Used to optimize decorating when there are no merged cells in a given
// frame.
bool _mergedRows = false;
bool _mergedColumns = false;
final List<int> _mergedRows = <int>[];
// bool _mergedColumns = false;

// Cached Table metrics
Map<int, _Span> _columnMetrics = <int, _Span>{};
Expand Down Expand Up @@ -609,8 +609,8 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {
void layoutChildSequence() {
// Reset for a new frame
_mergedVicinities.clear();
_mergedRows = false;
_mergedColumns = false;
_mergedRows.clear();
// _mergedColumns = false;

if (needsDelegateRebuild || didResize) {
// Recomputes the table metrics, invalidates any cached information.
Expand Down Expand Up @@ -749,8 +749,8 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {

// Merged cell handling
if (cellParentData.rowMergeStart != null) {
_mergedRows = true;
final int rowMergeStart = cellParentData.rowMergeStart!;
_mergedRows.add(rowMergeStart);
final int lastRow =
rowMergeStart + cellParentData.rowMergeSpan! - 1;
assert(_debugCheckMergeBounds(
Expand All @@ -771,6 +771,7 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {
_mergedVicinities[vicinity.copyWith(row: rowMergeStart)] = vicinity;
int nextRow = rowMergeStart + 1;
while (nextRow <= lastRow) {
_mergedRows.add(nextRow);
_mergedVicinities[vicinity.copyWith(row: nextRow)] = vicinity;
mergedRowHeight = mergedRowHeight! + _rowMetrics[nextRow]!.extent;
nextRow++;
Expand Down Expand Up @@ -927,12 +928,6 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {
}
}

@override
RenderBox? getChildFor(ChildVicinity vicinity) {
final RenderBox? child = super.getChildFor(vicinity);
return child ?? _getMergedChildFor(vicinity as TableVicinity);
}

RenderBox _getMergedChildFor(TableVicinity vicinity) {
assert(_mergedVicinities.keys.contains(vicinity));
final TableVicinity mergedVicinity = _mergedVicinities[vicinity]!;
Expand Down Expand Up @@ -1009,18 +1004,43 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {
LinkedHashMap<Rect, TableSpanDecoration>();
final LinkedHashMap<Rect, TableSpanDecoration> backgroundRows =
LinkedHashMap<Rect, TableSpanDecoration>();

final TableSpan columnSpan = _columnMetrics[leading.column]!.configuration;
for (int row = leading.row; row <= trailing.row; row++) {
final TableSpan rowSpan = _rowMetrics[row]!.configuration;
TableSpan rowSpan = _rowMetrics[row]!.configuration;
if (rowSpan.backgroundDecoration != null ||
rowSpan.foregroundDecoration != null) {
final RenderBox leadingCell = getChildFor(
TableVicinity(column: leading.column, row: row),
)!;
final RenderBox trailingCell = getChildFor(
TableVicinity(column: trailing.column, row: row),
)!;
rowSpan.foregroundDecoration != null || _mergedRows.contains(row)) {
final List<(RenderBox, RenderBox)> decorationCells = <(RenderBox, RenderBox)>[];
late RenderBox? leadingCell;
late RenderBox? trailingCell;
if (_mergedRows.isEmpty || !_mergedRows.contains(row)) {
// One decoration across the whole row.
decorationCells.add((
getChildFor(TableVicinity(column: leading.column, row: row))!, // leading
getChildFor(TableVicinity(column: trailing.column, row: row))!, // trailing
));
} else {
// Walk through the columns to separate merged rows for decorating. A
// merged row takes the decoration of its leading row.
int currentColumn = leading.column;
while (currentColumn <= trailing.column) {
TableVicinity vicinity = TableVicinity(column: currentColumn, row: row,);
leadingCell = getChildFor(vicinity) ?? _getMergedChildFor(vicinity);
if (parentDataOf(leadingCell).rowMergeStart != null) {
// Merged cell decorated individually.
decorationCells.add((leadingCell, leadingCell));
currentColumn++;
continue;
}
RenderBox? nextCell = leadingCell;
while (nextCell != null && parentDataOf(nextCell).rowMergeStart == null) {
trailingCell = nextCell;
vicinity = vicinity.copyWith(column: currentColumn++);
nextCell = getChildFor(vicinity);
}
decorationCells.add((leadingCell, trailingCell!));
currentColumn--;
}
}

Rect getRowRect(bool consumePadding) {
final ({double leading, double trailing}) offsetCorrection =
Expand All @@ -1031,13 +1051,13 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {
)
: (leading: 0.0, trailing: 0.0);
return Rect.fromPoints(
parentDataOf(leadingCell).paintOffset! +
parentDataOf(leadingCell!).paintOffset! +
offset -
Offset(
columnSpan.padding.leading - offsetCorrection.leading,
consumePadding ? rowSpan.padding.leading : 0.0,
),
parentDataOf(trailingCell).paintOffset! +
parentDataOf(trailingCell!).paintOffset! +
offset +
Offset(trailingCell.size.width, trailingCell.size.height) +
Offset(
Expand All @@ -1047,15 +1067,30 @@ class RenderTableViewport extends RenderTwoDimensionalViewport {
);
}

if (rowSpan.backgroundDecoration != null) {
final Rect rect =
getRowRect(rowSpan.backgroundDecoration!.consumeSpanPadding);
backgroundRows[rect] = rowSpan.backgroundDecoration!;
}
if (rowSpan.foregroundDecoration != null) {
final Rect rect =
getRowRect(rowSpan.foregroundDecoration!.consumeSpanPadding);
foregroundRows[rect] = rowSpan.foregroundDecoration!;
for (final (RenderBox, RenderBox) span in decorationCells) {
(leadingCell, trailingCell) = span;
// If this was a merged cell, the decoration is defined by the leading
// cell, which may come from a different row.
final int rowIndex = parentDataOf(leadingCell).rowMergeStart ?? parentDataOf(leadingCell).tableVicinity.row;
rowSpan = _rowMetrics[rowIndex]!.configuration;
if (rowSpan.backgroundDecoration != null) {
final Rect rect =
getRowRect(rowSpan.backgroundDecoration!.consumeSpanPadding);
// We could have already added this rect if it came from a merged
// cell.
if (!backgroundRows.keys.contains(rect)) {
backgroundRows[rect] = rowSpan.backgroundDecoration!;
}
}
if (rowSpan.foregroundDecoration != null) {
final Rect rect =
getRowRect(rowSpan.foregroundDecoration!.consumeSpanPadding);
// We could have already added this rect if it came from a merged
// cell.
if (!foregroundRows.keys.contains(rect)) {
foregroundRows[rect] = rowSpan.foregroundDecoration!;
}
}
}
}
}
Expand Down