render method
Diffs backBuffer against _frontBuffer and writes minimal ANSI escape sequences to out.
Implementation
void render(Buffer backBuffer, StringSink out) {
Tracer.record(_traceRenderId, Phase.begin, TraceCategory.paint);
try {
bool sizeChanged = _firstFrame;
_firstFrame = false;
if (mode == RenderingMode.inline) {
if (_lastHeight > 0) {
// Move cursor back up to the top-left of the inline block
out.write('\x1b[${_lastHeight}F');
} else {
// First frame: move to the next line to avoid printing TUI inline on the command prompt line
out.write('\n\r');
}
if (backBuffer.width != _frontBuffer.width ||
backBuffer.height != _frontBuffer.height) {
_frontBuffer.resize(backBuffer.width, backBuffer.height);
sizeChanged = true;
}
} else {
if (backBuffer.width != _frontBuffer.width ||
backBuffer.height != _frontBuffer.height ||
sizeChanged) {
_frontBuffer.resize(backBuffer.width, backBuffer.height);
sizeChanged = true;
// Clear screen and reset cursor position to top-left
out.write('\x1b[2J\x1b[H');
}
}
int activeFg = 0;
int activeBg = 0;
int activeMod = 0;
int cursorX = 0;
int cursorY = 0;
final width = backBuffer.width;
final height = backBuffer.height;
final backChars = backBuffer.characters;
final backAttr = backBuffer.attributes;
final frontChars = _frontBuffer.characters;
final frontAttr = _frontBuffer.attributes;
for (var y = 0; y < height; y++) {
var x = 0;
final rowOffset = y * width;
while (x < width) {
final idx = rowOffset + x;
final attrIdx = idx * 3;
final changed =
sizeChanged ||
(backChars[idx] != frontChars[idx] ||
backAttr[attrIdx + 0] != frontAttr[attrIdx + 0] ||
backAttr[attrIdx + 1] != frontAttr[attrIdx + 1] ||
backAttr[attrIdx + 2] != frontAttr[attrIdx + 2]);
if (changed) {
final runStart = x;
var runEnd = x;
// Find the end of the contiguous run of changed cells in the current row
while (runEnd < width) {
final ridx = rowOffset + runEnd;
final rattrIdx = ridx * 3;
if (sizeChanged ||
backChars[ridx] != frontChars[ridx] ||
backAttr[rattrIdx + 0] != frontAttr[rattrIdx + 0] ||
backAttr[rattrIdx + 1] != frontAttr[rattrIdx + 1] ||
backAttr[rattrIdx + 2] != frontAttr[rattrIdx + 2]) {
runEnd++;
} else {
break;
}
}
// Move cursor to (runStart, y) relative or absolute
if (mode == RenderingMode.inline) {
_moveCursorRelative(out, cursorX, cursorY, runStart, y);
cursorX = runStart;
cursorY = y;
} else {
out.write('\x1b[${y + 1};${runStart + 1}H');
}
// Render each cell in the run
for (var rx = runStart; rx < runEnd; rx++) {
final rxIdx = rowOffset + rx;
final rxAttrIdx = rxIdx * 3;
final cellFg = backAttr[rxAttrIdx + 0];
final cellBg = backAttr[rxAttrIdx + 1];
final cellMod = backAttr[rxAttrIdx + 2];
_writeStyleTransitionPrims(
out,
activeFg,
activeBg,
activeMod,
cellFg,
cellBg,
cellMod,
);
activeFg = cellFg;
activeBg = cellBg;
activeMod = cellMod;
out.write(backChars[rxIdx]);
frontChars[rxIdx] = backChars[rxIdx];
frontAttr[rxAttrIdx + 0] = backAttr[rxAttrIdx + 0];
frontAttr[rxAttrIdx + 1] = backAttr[rxAttrIdx + 1];
frontAttr[rxAttrIdx + 2] = backAttr[rxAttrIdx + 2];
if (mode == RenderingMode.inline) {
cursorX++;
}
}
x = runEnd;
} else {
x++;
}
}
}
if (activeFg != 0 || activeBg != 0 || activeMod != 0) {
out.write('\x1b[0m');
}
if (mode == RenderingMode.inline) {
// Position cursor at the beginning of the line immediately following the inline block
_moveCursorRelative(out, cursorX, cursorY, 0, backBuffer.height);
_lastHeight = backBuffer.height;
}
} finally {
Tracer.record(_traceRenderId, Phase.end, TraceCategory.paint);
}
}