feat: Improve Sixel rendering stability by adjusting output height and anchoring behavior
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -68,7 +68,8 @@ class CliGameLoop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout.write('\x1b[?25l\x1b[2J');
|
// Disable Sixel scrolling mode so frames overwrite in-place.
|
||||||
|
stdout.write('\x1b[?80l\x1b[?25l\x1b[2J');
|
||||||
|
|
||||||
_stdinSubscription = _stdinStream.listen(_handleInput);
|
_stdinSubscription = _stdinStream.listen(_handleInput);
|
||||||
_stopwatch.start();
|
_stopwatch.start();
|
||||||
@@ -101,7 +102,8 @@ class CliGameLoop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stdout.hasTerminal) {
|
if (stdout.hasTerminal) {
|
||||||
stdout.write('\x1b[0m\x1b[?25h');
|
// Restore scrolling Sixel mode and cursor visibility.
|
||||||
|
stdout.write('\x1b[0m\x1b[?80h\x1b[?25h');
|
||||||
}
|
}
|
||||||
|
|
||||||
_isRunning = false;
|
_isRunning = false;
|
||||||
@@ -200,12 +202,15 @@ class CliGameLoop {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final int safeCols = cols > 1 ? cols - 1 : cols;
|
||||||
final String hint = _buildShortcutHintText();
|
final String hint = _buildShortcutHintText();
|
||||||
final String visible = hint.length > cols ? hint.substring(0, cols) : hint;
|
final String visible = hint.length > safeCols
|
||||||
final String padded = visible.padRight(cols);
|
? hint.substring(0, safeCols)
|
||||||
|
: hint;
|
||||||
|
final String padded = visible.padRight(safeCols);
|
||||||
|
|
||||||
// Draw an overlay line without disturbing the renderer's cursor position.
|
// Draw an overlay line without disturbing the renderer's cursor position.
|
||||||
stdout.write('\x1b[s\x1b[1;1H\x1b[0m\x1b[2m$padded\x1b[0m\x1b[u');
|
stdout.write('\x1b[s\x1b[1;1H\x1b[0m\x1b[2m\x1b[2K$padded\x1b[0m\x1b[u');
|
||||||
}
|
}
|
||||||
|
|
||||||
String _buildShortcutHintText() {
|
String _buildShortcutHintText() {
|
||||||
|
|||||||
@@ -200,14 +200,22 @@ class SixelRenderer extends CliRendererBackend<String> {
|
|||||||
|
|
||||||
// Horizontal: cell-width estimates vary by terminal/font and can cause
|
// Horizontal: cell-width estimates vary by terminal/font and can cause
|
||||||
// right-shift clipping, so keep the image anchored at column 0.
|
// right-shift clipping, so keep the image anchored at column 0.
|
||||||
// Vertical: keep one spare row to avoid scroll, but anchor to the bottom
|
// Vertical: force top anchoring to keep repaint location stable across
|
||||||
// of the viewport so we avoid obvious empty space under the image.
|
// terminals that still scroll when rendering near the bottom edge.
|
||||||
final int imageRows = math.max(
|
|
||||||
1,
|
|
||||||
(_outputHeight / _defaultLineHeightPx).ceil() + _terminalRowSafetyMargin,
|
|
||||||
);
|
|
||||||
_offsetColumns = 0;
|
_offsetColumns = 0;
|
||||||
_offsetRows = math.max(0, terminalRows - imageRows);
|
_offsetRows = 0;
|
||||||
|
|
||||||
|
// Clamp output height to a conservative terminal-row budget so Sixel data
|
||||||
|
// cannot extend beyond the viewport and trigger implicit scroll.
|
||||||
|
final int maxImageRows = math.max(
|
||||||
|
1,
|
||||||
|
terminalRows - _terminalRowSafetyMargin,
|
||||||
|
);
|
||||||
|
final int maxOutputHeight = maxImageRows * _defaultLineHeightPx;
|
||||||
|
if (_outputHeight > maxOutputHeight) {
|
||||||
|
_outputHeight = maxOutputHeight;
|
||||||
|
_outputWidth = math.max(1, (_outputHeight * _targetAspectRatio).floor());
|
||||||
|
}
|
||||||
|
|
||||||
if (_offsetColumns != previousOffsetColumns ||
|
if (_offsetColumns != previousOffsetColumns ||
|
||||||
_offsetRows != previousOffsetRows ||
|
_offsetRows != previousOffsetRows ||
|
||||||
|
|||||||
Reference in New Issue
Block a user