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 viewport = Viewport(
    buffer,
    Rect(offset.dx, offset.dy, size.width, size.height),
  );
  final progress = widget as LinearProgressIndicator;
  if (size.width <= 0 || size.height <= 0) return;

  final clamped = progress.fraction.clamp(0.0, 1.0);
  final eased = progress.easing(clamped);

  final (int subWidth, int subHeight) = switch (progress.barType) {
    ProgressBarType.braille => (2, 4),
    ProgressBarType.quads => (2, 2),
    ProgressBarType.blocks =>
      progress.smooth
          ? switch (progress.direction) {
              ProgressDirection.leftToRight ||
              ProgressDirection.rightToLeft => (8, 1),
              ProgressDirection.topToBottom ||
              ProgressDirection.bottomToTop => (1, 8),
            }
          : (1, 1),
  };

  final isHorizontal =
      progress.direction == ProgressDirection.leftToRight ||
      progress.direction == ProgressDirection.rightToLeft;
  final mainAxisCells = isHorizontal ? size.width : size.height;
  final crossAxisCells = isHorizontal ? size.height : size.width;
  final mainSubunitsPerCell = isHorizontal ? subWidth : subHeight;
  final crossSubunitsPerCell = isHorizontal ? subHeight : subWidth;

  final gridMainAxis = mainAxisCells * mainSubunitsPerCell;
  final gridCrossAxis = crossAxisCells * crossSubunitsPerCell;

  final totalFilledSpan = (eased * gridMainAxis).round();
  final totalFilledPrecise = (eased * (gridMainAxis * gridCrossAxis)).round();

  String? pctText;
  int? pctStartX;
  int? pctY;
  if (progress.showPercentage) {
    pctText = '${(eased * 100).toInt().clamp(0, 100)}%';
    if (pctText.length <= size.width) {
      pctStartX = ((size.width - pctText.length) / 2).floor();
      pctY = size.height ~/ 2;
    } else {
      pctText = null;
    }
  }

  final colors = progress.colorBuilder?.call(clamped, eased);
  List<GradientStop>? fgStops = colors?.fg;
  List<GradientStop>? bgStops = colors?.bg;

  // ignore: deprecated_member_use_from_same_package
  if (progress.colorBuilder == null &&
      (progress.startColor != null || progress.endColor != null)) {
    // ignore: deprecated_member_use_from_same_package
    final start =
        progress.startColor ??
        progress.style.foreground ??
        const Color(255, 255, 255);
    // ignore: deprecated_member_use_from_same_package
    final end =
        progress.endColor ??
        progress.style.foreground ??
        const Color(255, 255, 255);
    fgStops = [(color: start, stop: 0.0), (color: end, stop: 1.0)];
  }

  for (var y = 0; y < size.height; y++) {
    for (var x = 0; x < size.width; x++) {
      double cellT = 0.0;
      if (mainAxisCells > 1) {
        final mainCellIndex = switch (progress.direction) {
          ProgressDirection.leftToRight => x,
          ProgressDirection.rightToLeft => size.width - 1 - x,
          ProgressDirection.topToBottom => y,
          ProgressDirection.bottomToTop => size.height - 1 - y,
        };
        cellT = mainCellIndex / (mainAxisCells - 1);
      }

      final cellFg =
          _interpolateColor(fgStops, cellT) ?? progress.style.foreground;
      final cellBg =
          _interpolateColor(bgStops, cellT) ?? progress.style.background;

      bool invertColors = false;

      String char = ' ';
      if (pctText != null &&
          y == pctY &&
          x >= pctStartX! &&
          x < pctStartX + pctText.length) {
        char = pctText[x - pctStartX];
      } else {
        final baseGx = x * subWidth;
        final baseGy = y * subHeight;
        final isSpan = progress.crossAxisFill == CrossAxisFill.span;
        final gridW = size.width * subWidth;
        final gridH = size.height * subHeight;

        final threshold = isSpan
            ? switch (progress.direction) {
                ProgressDirection.leftToRight => totalFilledSpan - baseGx,
                ProgressDirection.rightToLeft =>
                  (gridW - 1) - baseGx - totalFilledSpan,
                ProgressDirection.topToBottom => totalFilledSpan - baseGy,
                ProgressDirection.bottomToTop =>
                  (gridH - 1) - baseGy - totalFilledSpan,
              }
            : switch (progress.direction) {
                ProgressDirection.leftToRight =>
                  totalFilledPrecise - (baseGx * gridCrossAxis + baseGy),
                ProgressDirection.rightToLeft =>
                  totalFilledPrecise -
                      ((gridW - 1 - baseGx) * gridCrossAxis + baseGy),
                ProgressDirection.topToBottom =>
                  totalFilledPrecise - (baseGy * gridCrossAxis + baseGx),
                ProgressDirection.bottomToTop =>
                  totalFilledPrecise -
                      ((gridH - 1 - baseGy) * gridCrossAxis + baseGx),
              };

        if (progress.barType == ProgressBarType.braille) {
          int offset = 0;
          if (isSpan) {
            switch (progress.direction) {
              case ProgressDirection.leftToRight:
                if (0 < threshold) offset |= 71; // 1 | 2 | 4 | 64
                if (1 < threshold) offset |= 184; // 8 | 16 | 32 | 128
              case ProgressDirection.rightToLeft:
                if (0 > threshold) offset |= 71;
                if (1 > threshold) offset |= 184;
              case ProgressDirection.topToBottom:
                if (0 < threshold) offset |= 9; // dy=0: 1 | 8
                if (1 < threshold) offset |= 18; // dy=1: 2 | 16
                if (2 < threshold) offset |= 36; // dy=2: 4 | 32
                if (3 < threshold) offset |= 192; // dy=3: 64 | 128
              case ProgressDirection.bottomToTop:
                if (0 > threshold) offset |= 9;
                if (1 > threshold) offset |= 18;
                if (2 > threshold) offset |= 36;
                if (3 > threshold) offset |= 192;
            }
          } else {
            switch (progress.direction) {
              case ProgressDirection.leftToRight:
                if (0 < threshold) offset |= 1;
                if (1 < threshold) offset |= 2;
                if (2 < threshold) offset |= 4;
                if (gridCrossAxis < threshold) offset |= 8;
                if (gridCrossAxis + 1 < threshold) offset |= 16;
                if (gridCrossAxis + 2 < threshold) offset |= 32;
                if (3 < threshold) offset |= 64;
                if (gridCrossAxis + 3 < threshold) offset |= 128;
              case ProgressDirection.rightToLeft:
                if (0 < threshold) offset |= 1;
                if (1 < threshold) offset |= 2;
                if (2 < threshold) offset |= 4;
                if (-gridCrossAxis < threshold) offset |= 8;
                if (1 - gridCrossAxis < threshold) offset |= 16;
                if (2 - gridCrossAxis < threshold) offset |= 32;
                if (3 < threshold) offset |= 64;
                if (3 - gridCrossAxis < threshold) offset |= 128;
              case ProgressDirection.topToBottom:
                if (0 < threshold) offset |= 1;
                if (gridCrossAxis < threshold) offset |= 2;
                if (2 * gridCrossAxis < threshold) offset |= 4;
                if (1 < threshold) offset |= 8;
                if (gridCrossAxis + 1 < threshold) offset |= 16;
                if (2 * gridCrossAxis + 1 < threshold) offset |= 32;
                if (3 * gridCrossAxis < threshold) offset |= 64;
                if (3 * gridCrossAxis + 1 < threshold) offset |= 128;
              case ProgressDirection.bottomToTop:
                if (0 < threshold) offset |= 1;
                if (-gridCrossAxis < threshold) offset |= 2;
                if (-2 * gridCrossAxis < threshold) offset |= 4;
                if (1 < threshold) offset |= 8;
                if (1 - gridCrossAxis < threshold) offset |= 16;
                if (1 - 2 * gridCrossAxis < threshold) offset |= 32;
                if (-3 * gridCrossAxis < threshold) offset |= 64;
                if (1 - 3 * gridCrossAxis < threshold) offset |= 128;
            }
          }
          char = String.fromCharCode(0x2800 + offset);
        } else if (progress.barType == ProgressBarType.quads) {
          if (progress.crossAxisFill == CrossAxisFill.span &&
              progress.smooth) {
            final maxSteps = isHorizontal ? size.width * 4 : size.height * 4;
            final filledSteps = (eased * maxSteps).round();
            final cellSteps = switch (progress.direction) {
              ProgressDirection.leftToRight => filledSteps - (x * 4),
              ProgressDirection.rightToLeft =>
                filledSteps - ((size.width - 1 - x) * 4),
              ProgressDirection.topToBottom => filledSteps - (y * 4),
              ProgressDirection.bottomToTop =>
                filledSteps - ((size.height - 1 - y) * 4),
            };

            final clamped = cellSteps.clamp(0, 4);
            if (isHorizontal) {
              const quadStepsH = [' ', '▖', '▌', '▙', '█'];
              char = quadStepsH[clamped];
            } else {
              const quadStepsV = [' ', '▖', '▄', '▙', '█'];
              char = quadStepsV[clamped];
            }
          } else {
            int offset = 0;
            if (isSpan) {
              switch (progress.direction) {
                case ProgressDirection.leftToRight:
                  if (0 < threshold) offset |= 5; // dx=0: 1 | 4
                  if (1 < threshold) offset |= 10; // dx=1: 2 | 8
                case ProgressDirection.rightToLeft:
                  if (0 > threshold) offset |= 5;
                  if (1 > threshold) offset |= 10;
                case ProgressDirection.topToBottom:
                  if (0 < threshold) offset |= 3; // dy=0: 1 | 2
                  if (1 < threshold) offset |= 12; // dy=1: 4 | 8
                case ProgressDirection.bottomToTop:
                  if (0 > threshold) offset |= 3;
                  if (1 > threshold) offset |= 12;
              }
            } else {
              switch (progress.direction) {
                case ProgressDirection.leftToRight:
                  if (0 < threshold) offset |= 1;
                  if (1 < threshold) offset |= 4;
                  if (gridCrossAxis < threshold) offset |= 2;
                  if (gridCrossAxis + 1 < threshold) offset |= 8;
                case ProgressDirection.rightToLeft:
                  if (0 < threshold) offset |= 1;
                  if (1 < threshold) offset |= 4;
                  if (-gridCrossAxis < threshold) offset |= 2;
                  if (1 - gridCrossAxis < threshold) offset |= 8;
                case ProgressDirection.topToBottom:
                  if (0 < threshold) offset |= 1;
                  if (1 < threshold) offset |= 2;
                  if (gridCrossAxis < threshold) offset |= 4;
                  if (gridCrossAxis + 1 < threshold) offset |= 8;
                case ProgressDirection.bottomToTop:
                  if (0 < threshold) offset |= 1;
                  if (1 < threshold) offset |= 2;
                  if (-gridCrossAxis < threshold) offset |= 4;
                  if (1 - gridCrossAxis < threshold) offset |= 8;
              }
            }
            const quads = [
              ' ',
              '▘',
              '▝',
              '▀',
              '▖',
              '▌',
              '▞',
              '▛',
              '▗',
              '▚',
              '▐',
              '▜',
              '▄',
              '▙',
              '▟',
              '█',
            ];
            char = quads[offset];
          }
        } else {
          int filledCount = 0;
          if (isSpan) {
            switch (progress.direction) {
              case ProgressDirection.leftToRight:
                filledCount = threshold.clamp(0, subWidth);
              case ProgressDirection.rightToLeft:
                filledCount = subWidth - (threshold + 1).clamp(0, subWidth);
              case ProgressDirection.topToBottom:
                filledCount = threshold.clamp(0, subHeight);
              case ProgressDirection.bottomToTop:
                filledCount = subHeight - (threshold + 1).clamp(0, subHeight);
            }
          } else {
            for (int dy = 0; dy < subHeight; dy++) {
              for (int dx = 0; dx < subWidth; dx++) {
                final val = switch (progress.direction) {
                  ProgressDirection.leftToRight => dx * gridCrossAxis + dy,
                  ProgressDirection.rightToLeft => dy - dx * gridCrossAxis,
                  ProgressDirection.topToBottom => dy * gridCrossAxis + dx,
                  ProgressDirection.bottomToTop => dx - dy * gridCrossAxis,
                };
                if (val < threshold) filledCount++;
              }
            }
          }

          bool canInvert = cellBg != null;

          if (subWidth > 1) {
            if (progress.direction == ProgressDirection.rightToLeft &&
                canInvert &&
                filledCount > 0 &&
                filledCount < 8) {
              char = LinearProgressIndicator
                  ._blocksHorizontal[(8 - filledCount).clamp(0, 8)];
              invertColors = true;
            } else {
              char = LinearProgressIndicator
                  ._blocksHorizontal[filledCount.clamp(0, 8)];
            }
          } else if (subHeight > 1) {
            if (progress.direction == ProgressDirection.topToBottom &&
                canInvert &&
                filledCount > 0 &&
                filledCount < 8) {
              char = LinearProgressIndicator
                  ._blocksVertical[(8 - filledCount).clamp(0, 8)];
              invertColors = true;
            } else {
              char = LinearProgressIndicator
                  ._blocksVertical[filledCount.clamp(0, 8)];
            }
          } else {
            char = filledCount > 0 ? '█' : ' ';
          }
        }
      }

      final renderFg = invertColors ? cellBg : cellFg;
      final renderBg = invertColors ? cellFg : cellBg;

      viewport.setAttributes(
        x,
        y,
        char: char,
        fg: renderFg?.argb,
        bg: renderBg?.argb,
        modifiers: progress.style.modifiers,
      );
    }
  }
}