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