render method
Renders the underlying widget to the provided buffer within the area.
Implementation
@override
void render(Buffer buffer, Rect area) {
final table = widget as Table;
if (table.headers.isEmpty ||
table.columnWidths.isEmpty ||
area.height <= 2 ||
area.width <= 0) {
return;
}
// 1. Render Headers
final headerSb = StringBuffer();
for (var i = 0; i < table.headers.length; i++) {
final width = i < table.columnWidths.length ? table.columnWidths[i] : 10;
final text = table.headers[i];
final chars = text.characters;
final padded = chars.length >= width
? chars.take(width).toString()
: chars.toString() + (' ' * (width - chars.length));
headerSb.write(padded);
if (i < table.headers.length - 1) headerSb.write(' ');
}
final headerChars = headerSb.toString().characters;
final headerStr = headerChars.length >= area.width
? headerChars.take(area.width).toString()
: headerChars.toString() + (' ' * (area.width - headerChars.length));
buffer.writeString(0, 0, headerStr, table.headerStyle);
// 2. Render Header Divider Line
final dividerChar = '─';
final divider = dividerChar * area.width;
buffer.writeString(0, 1, divider, table.borderStyle);
// 3. Render Rows with Scroll Adjustment
final usableHeight = area.height - 2;
table.adjustScroll(usableHeight);
// Synchronize cell elements list
while (cellElements.length < table.rows.length) {
cellElements.add(
List.filled(table.headers.length, null, growable: false),
);
}
if (cellElements.length > table.rows.length) {
cellElements.removeRange(table.rows.length, cellElements.length);
}
for (var r = 0; r < usableHeight; r++) {
final rowIdx = table.scrollOffset + r;
if (rowIdx >= table.rows.length) break;
final rowData = table.rows[rowIdx];
final isSelected = rowIdx == table.selectedRowIndex;
final currentStyle = isSelected ? table.selectedRowStyle : table.rowStyle;
int startX = 0;
for (var c = 0; c < table.headers.length; c++) {
final colWidth = c < table.columnWidths.length
? table.columnWidths[c]
: 10;
final cellData = c < rowData.length ? rowData[c] : '';
final cellRect = Rect(startX, 2 + r, colWidth, 1);
final vp = Viewport(buffer, cellRect);
if (cellData is Widget) {
// Pre-fill cell viewport with space using currentStyle
vp.fill(Cell(' ', currentStyle));
// Mount / update child element
Element? cellEl = cellElements[rowIdx][c];
if (cellEl != null &&
cellEl.widget.runtimeType == cellData.runtimeType) {
cellEl.update(cellData);
} else {
cellEl = cellData.createElement();
cellEl.mount(this);
cellElements[rowIdx][c] = cellEl;
}
cellEl.render(vp, Rect(0, 0, colWidth, 1));
// We do NOT apply selection style on top of the rendered cell cells
// to prevent negative image and custom styling override.
} else {
// Standard text rendering
final cellText = cellData.toString();
final chars = cellText.characters;
final padded = chars.length >= colWidth
? chars.take(colWidth).toString()
: chars.toString() + (' ' * (colWidth - chars.length));
vp.writeString(0, 0, padded, currentStyle);
}
// Render spacing between columns
if (c < table.headers.length - 1) {
final sepX = startX + colWidth;
if (sepX < area.width) {
buffer.writeString(sepX, 2 + r, ' ', currentStyle);
}
}
startX += colWidth + 1;
}
// Pad remainder of the row to fill area.width
if (startX < area.width) {
final remainder = area.width - startX;
buffer.writeString(startX, 2 + r, ' ' * remainder, currentStyle);
}
}
}