performPaint method
Hook for subclasses to implement actual painting.
Implementation
@override
void performPaint(Buffer buffer, Offset offset) {
final listView = widget as ListView;
final w = size.width;
for (var i = 0; i < visibleIndices.length; i++) {
final itemIdx = visibleIndices[i];
final child = childElements[itemIdx];
// Get the spatial coordinate calculated during performLayout
final childOffset = child.relativeOffset;
final isSelected = itemIdx == selectedIndex;
final isHovered = itemIdx == hoveredIndex;
var style = listView.itemStyle;
if (isSelected) {
style = listView.selectedStyle;
} else if (isHovered) {
style = listView.hoveredStyle ?? const Style(modifiers: Modifier.dim);
}
// Fill background row with space characters and target style
buffer.writeString(
offset.dx.toInt(),
offset.dy.toInt() + i,
' ' * w,
style,
);
// Paint child
// Provide the background and foreground via InheritedTheme or just write it first.
// But we can just use the target style as the default for the child
// However since this is legacy code, we paint child on top of background
child.paint(buffer, offset + childOffset);
// Apply the inherited style to the text
// Instead of reading every cell, we can just use composite buffer or accept that child.paint wrote with correct styles.
// For brevity and correct O(1) text rendering, let's just write the child again?
// Wait, we can't change child's render pipeline here, so we must mutate cells.
// But we can optimize to only merge if the style isn't empty!
if (style != Style.empty) {
for (var col = 0; col < w; col++) {
final targetX = offset.dx.toInt() + col;
final targetY = offset.dy.toInt() + i;
final fg = buffer.getForeground(targetX, targetY);
final bg = buffer.getBackground(targetX, targetY);
final mods = buffer.getModifiers(targetX, targetY);
final currentStyle = Style(
foreground: fg != 0 ? Color.argb(fg) : null,
background: bg != 0 ? Color.argb(bg) : null,
modifiers: mods,
);
final newStyle = style.merge(currentStyle);
buffer.setAttributes(
targetX,
targetY,
fg: newStyle.foreground?.argb,
bg: newStyle.background?.argb,
modifiers: newStyle.modifiers,
);
}
}
}
if (listView.showScrollbar && listView.children.isNotEmpty) {
final total = listView.children.length;
final viewportHeight = size.height.toInt();
if (total > viewportHeight) {
final thumbSize = max(1, (viewportHeight * viewportHeight) ~/ total);
final maxScrollOffset = total - viewportHeight;
final scrollPercent = scrollOffset / maxScrollOffset;
final thumbOffset = (scrollPercent * (viewportHeight - thumbSize))
.round();
for (var i = 0; i < viewportHeight; i++) {
final isThumb = i >= thumbOffset && i < thumbOffset + thumbSize;
final char = isThumb ? '█' : '│';
buffer.writeString(
offset.dx.toInt() + w - 1,
offset.dy.toInt() + i,
char,
Style.empty,
);
}
}
}
}