performPaint method

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

Paints the flame chart timeline. Maps temporal spans into spatial columns using w.offsetX and w.zoomLevel.

Implementation

@override
void performPaint(Buffer buffer, Offset offset) {
  final w = widget as TimelineCanvas;
  final width = size.width;
  final height = size.height;

  final startX = offset.dx;
  final startY = offset.dy;

  void drawCell(int cx, int cy, String char, Style style) {
    if (cx >= startX &&
        cx < startX + width &&
        cy >= startY &&
        cy < startY + height) {
      _safeSetCell(buffer, cx, cy, char, style);
    }
  }

  final borderStyle = const Style(foreground: Color(120, 120, 120));

  // Paint left/right borders
  for (var y = 0; y < height; y++) {
    drawCell(startX, startY + y, '│', borderStyle);
    drawCell(startX + width - 1, startY + y, '│', borderStyle);
  }

  if (w.selectionStartUs != null && w.selectionEndUs != null) {
    final startUs = w.selectionStartUs!.toDouble();
    final endUs = w.selectionEndUs!.toDouble();

    final startColF = (startUs - w.offsetX) / w.zoomLevel;
    final endColF = (endUs - w.offsetX) / w.zoomLevel;

    final startCol = startColF.floor().clamp(0, width - 3);
    final endCol = endColF.floor().clamp(0, width - 3);

    if (endCol >= 0 && startCol < width - 2) {
      final highlightStyle = const Style(background: Color(40, 40, 80));
      for (var col = startCol; col <= endCol; col++) {
        final x = startX + 1 + col;
        for (var cy = startY; cy < startY + height; cy++) {
          _safeSetCell(buffer, x, cy, ' ', highlightStyle);
        }
      }
    }
  }

  final viewportStartUs = w.offsetX;
  final viewportEndUs = w.offsetX + (width - 2) * w.zoomLevel;
  final visibleSpans = _getCulledSpans(
    w.spans,
    viewportStartUs,
    viewportEndUs,
  );
  final mainSpans = visibleSpans;

  final Set<int> drawnCells = {};

  for (final span in mainSpans) {
    final depth = span.depth;
    final visualY = depth - w.offsetY;
    if (visualY < 0 || visualY >= height) continue;
    final y = startY + visualY;

    final spanStartUs = span.startUs.toDouble();
    final spanEndUs = span.endUs.toDouble();

    final startColF = (spanStartUs - w.offsetX) / w.zoomLevel;
    final endColF = (spanEndUs - w.offsetX) / w.zoomLevel;

    final startCol = startColF.floor().clamp(0, width - 3);
    final endCol = endColF.floor().clamp(0, width - 3);

    final style = getCategoryStyle(span.category, span.name);

    if (spanStartUs == spanEndUs) {
      final x = startX + 1 + startCol;
      final cellKey = (x << 16) | y;
      if (drawnCells.contains(cellKey)) continue;

      final bgArgb = buffer.getBackground(x, y);
      final bg = bgArgb == 0 ? null : Color.argb(bgArgb);
      drawCell(x, y, '│', style.merge(Style(background: bg)));
      drawnCells.add(cellKey);
      continue;
    }

    final bgColStyle = Style(
      background: style.foreground,
      foreground: Colors.white,
    );
    final fallbackStyle = style;

    final nameWithPrefix = '▼ ${span.displayLabel}';
    final spanColWidth = endCol - startCol + 1;

    for (var col = startCol; col <= endCol; col++) {
      final x = startX + 1 + col;
      final cellKey = (x << 16) | y;
      if (drawnCells.contains(cellKey)) continue;

      final cellStart = w.offsetX + col * w.zoomLevel;
      final cellEnd = cellStart + w.zoomLevel;

      final cov = _coverage(spanStartUs, spanEndUs, cellStart, cellEnd);
      if (cov > 0) {
        String char;
        Style cellStyle = bgColStyle;

        if (cov > 0.99) {
          final relativeCol = col - startCol;
          if (spanColWidth >= nameWithPrefix.length &&
              relativeCol >= 0 &&
              relativeCol < nameWithPrefix.length) {
            char = nameWithPrefix[relativeCol];
          } else {
            char = ' ';
          }
          drawnCells.add(cellKey);
        } else {
          if (startCol == endCol) {
            char = cov <= 0.1 ? '│' : _getFractionalBlock(cov);
            cellStyle = fallbackStyle;
          } else if (col == startCol) {
            char = _getFractionalBlock(1.0 - cov);
            cellStyle = style.merge(
              Style(
                foreground: null,
                background: style.foreground ?? const Color(255, 255, 255),
              ),
            );
          } else {
            char = _getFractionalBlock(cov);
            cellStyle = fallbackStyle;
          }
          if (char == '│' || char == '█') {
            drawnCells.add(cellKey);
          }
        }
        drawCell(x, y, char, cellStyle);
      }
    }
  }

  // Draw Caliper Overlay
  if (w.measureStartMs != null && w.measureEndMs != null) {
    final startUs = w.measureStartMs! * 1000.0;
    final endUs = w.measureEndMs! * 1000.0;
    final col1 = ((startUs - w.offsetX) / w.zoomLevel).round();
    final col2 = ((endUs - w.offsetX) / w.zoomLevel).round();
    final leftCol = min(col1, col2).clamp(0, width - 3) + 1;
    final rightCol = max(col1, col2).clamp(0, width - 3) + 1;
    final spanWidth = rightCol - leftCol + 1;
    final durationMs = (endUs - startUs).abs() / 1000.0;
    final text = ' ${durationMs.toStringAsFixed(1)}ms ';
    String caliperStr;
    if (spanWidth >= text.length + 4) {
      final dashes = spanWidth - 2 - text.length;
      final leftDashes = dashes ~/ 2;
      final rightDashes = dashes - leftDashes;
      caliperStr = '├${'─' * leftDashes}$text${'─' * rightDashes}┤';
    } else if (spanWidth >= 2) {
      caliperStr = '├${'─' * (spanWidth - 2)}┤';
    } else {
      caliperStr = '│';
    }

    final caliperStyle = const Style(
      foreground: Color(0, 255, 255),
      modifiers: Modifier.bold,
    );
    final caliperRow = startY - 1;
    // We draw directly into the buffer for startY - 1, bypassing drawCell's cy >= startY check
    for (var i = 0; i < caliperStr.length; i++) {
      final cx = startX + leftCol + i;
      if (cx >= startX && cx < startX + width) {
        final bgArgb = buffer.getBackground(cx, caliperRow);
        final bg = bgArgb == 0 ? null : Color.argb(bgArgb);
        _safeSetCell(
          buffer,
          cx,
          caliperRow,
          caliperStr[i],
          caliperStyle.merge(Style(background: bg)),
        );
      }
    }

    for (var y = caliperRow + 1; y < startY + height; y++) {
      final bgArgb1 = buffer.getBackground(startX + leftCol, y);
      final bg1 = bgArgb1 == 0 ? null : Color.argb(bgArgb1);
      drawCell(
        startX + leftCol,
        y,
        '│',
        caliperStyle.merge(Style(background: bg1)),
      );

      if (rightCol > leftCol) {
        final bgArgb2 = buffer.getBackground(startX + rightCol, y);
        final bg2 = bgArgb2 == 0 ? null : Color.argb(bgArgb2);
        drawCell(
          startX + rightCol,
          y,
          '│',
          caliperStyle.merge(Style(background: bg2)),
        );
      }
    }
  }
}