performPaint method
Hook for subclasses to implement actual painting.
Implementation
@override
void performPaint(Buffer buffer, Offset offset) {
final modal = widget as ModalOverlay;
final w = size.width;
final h = size.height;
if (w <= 0 || h <= 0) return;
// 1. Check/force focus to modal if it got lost/not set
bool anyFocused = false;
for (final node in modal.modalFocusNodes) {
if (node.isFocused) {
anyFocused = true;
break;
}
}
if (!anyFocused && modal.modalFocusNodes.isNotEmpty) {
modal.modalFocusNodes.first.requestFocus();
}
// 2. Draw the background scrim (dimming the cells beneath the modal)
for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) {
final targetX = (offset.dx + x).toInt();
final targetY = (offset.dy + y).toInt();
final isInsideDialog =
x >= modal.dialogBounds.x &&
x < modal.dialogBounds.x + modal.dialogBounds.width &&
y >= modal.dialogBounds.y &&
y < modal.dialogBounds.y + modal.dialogBounds.height;
if (!isInsideDialog) {
final modifiers = buffer.getModifiers(targetX, targetY);
buffer.setAttributes(
targetX,
targetY,
modifiers: modifiers | Modifier.dim,
);
}
}
}
// 3. Draw the central dialog box inside dialogBounds
final dialogX = offset.dx + modal.dialogBounds.x;
final dialogY = offset.dy + modal.dialogBounds.y;
final dw = modal.dialogBounds.width;
final dh = modal.dialogBounds.height;
if (dw < 2 || dh < 2) {
for (var y = 0; y < dh; y++) {
for (var x = 0; x < dw; x++) {
buffer.setAttributes(
(dialogX + x).toInt(),
(dialogY + y).toInt(),
char: ' ',
fg: modal.borderStyle.foreground?.argb,
bg: modal.borderStyle.background?.argb,
modifiers: modal.borderStyle.modifiers,
);
}
}
return;
}
// Draw top border
final topBorder =
modal.borderChars[0] +
modal.borderChars[1] * (dw - 2) +
modal.borderChars[2];
buffer.writeString(dialogX, dialogY, topBorder, modal.borderStyle);
// Overlay title
if (modal.title.isNotEmpty) {
final titleChars = modal.title.characters;
final maxTitleLen = dw - 4;
String displayedTitle;
if (titleChars.length > maxTitleLen) {
final cutLen = dw - 7;
if (cutLen > 0) {
displayedTitle = ' ${titleChars.take(cutLen).toString()}... ';
} else {
displayedTitle = '';
}
} else {
displayedTitle = ' ${modal.title} ';
}
if (displayedTitle.isNotEmpty) {
final dispChars = displayedTitle.characters;
final titleX = max(
1,
min(dw - 2, ((dw - dispChars.length) / 2).floor()),
);
buffer.writeString(
dialogX + titleX,
dialogY,
displayedTitle,
modal.titleStyle,
);
}
}
// Side borders
for (var y = 1; y < dh - 1; y++) {
buffer.writeString(
dialogX,
dialogY + y,
modal.borderChars[3],
modal.borderStyle,
);
buffer.writeString(
dialogX + dw - 1,
dialogY + y,
modal.borderChars[5],
modal.borderStyle,
);
}
// Bottom border
final bottomBorder =
modal.borderChars[6] +
modal.borderChars[7] * (dw - 2) +
modal.borderChars[8];
buffer.writeString(
dialogX,
dialogY + dh - 1,
bottomBorder,
modal.borderStyle,
);
// Render child content viewport
final contentArea = Rect(dialogX + 1, dialogY + 1, dw - 2, dh - 2);
final contentViewport = Viewport(buffer, contentArea);
contentViewport.fillAttributes(
char: ' ',
fg: modal.backgroundStyle.foreground?.argb,
bg: modal.backgroundStyle.background?.argb,
modifiers: modal.backgroundStyle.modifiers,
);
if (childElement != null) {
childElement!.paint(contentViewport, Offset.zero);
}
}