performPaint method

  1. @override
void performPaint(
  1. Buffer buffer,
  2. Offset offset
)
override

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,
        );
      }
    }
  }
}