panel method
Renders just the debug panel (cached for performance).
Implementation
String panel({int? terminalWidthOverride}) {
final m = metrics;
final currentFrame = m?.frameCount ?? 0;
final customHash = Object.hashAll(
customMetrics.entries.map((e) => Object.hash(e.key, e.value)),
);
// Include mode, message count, and output count in cache key.
final msgCount = messageEntries.length;
final outCount = outputEntries.length;
final cacheKey = Object.hash(
currentFrame,
panelWidth,
customHash,
mode,
msgCount,
outCount,
);
// Use cached panel if nothing has changed.
if (_cachedPanel != null && _cachedCacheKey == cacheKey) {
return _cachedPanel!;
}
final label = Style().foreground(Colors.yellow).bold();
final dim = Style().dim();
final content = StringBuffer();
// --- Metrics section ---
if (mode == DebugOverlayMode.metrics || mode == DebugOverlayMode.all) {
if (mode == DebugOverlayMode.all) {
content.writeln(dim.render('── Metrics ──'));
}
final avgFps = m?.averageFps ?? 0.0;
final avgFrameTimeUs = m?.averageFrameTime.inMicroseconds ?? 0;
final avgRenderTimeUs = m?.averageRenderDuration.inMicroseconds ?? 0;
final frameCount = m?.frameCount ?? 0;
final skippedFrames = m?.skippedFrames ?? 0;
final renderPct = m?.renderTimePercentage ?? 0.0;
content
..writeln(
'${label.render('FPS:')} ${avgFps.toStringAsFixed(1)} '
'(${m?.minFps.toStringAsFixed(0) ?? 0}-${m?.maxFps.toStringAsFixed(0) ?? 0})',
)
..writeln(
'${label.render('Frame Time:')} ${(avgFrameTimeUs / 1000).toStringAsFixed(2)}ms',
)
..writeln(
'${label.render('Render Time:')} $avgRenderTimeUsµs '
'(${renderPct.toStringAsFixed(1)}%)',
)
..writeln(
'${label.render('Frames:')} $frameCount (skipped: $skippedFrames)',
)
..writeln('${label.render('Cells:')} ${terminalWidth * terminalHeight}')
..writeln('${label.render('Renderer:')} $rendererLabel');
for (final entry in customMetrics.entries) {
content.writeln('${label.render('${entry.key}:')} ${entry.value}');
}
}
// --- Messages section ---
if (mode == DebugOverlayMode.messages || mode == DebugOverlayMode.all) {
if (content.isNotEmpty) content.writeln();
if (mode == DebugOverlayMode.all) {
content.writeln(dim.render('── Messages ──'));
}
if (messageEntries.isEmpty) {
content.writeln(dim.render('(no messages)'));
} else {
final display = messageEntries.take(maxDisplayMessages);
for (final entry in display) {
final elapsed = entry.processingTime.inMicroseconds;
final line = '${entry.summary} ${dim.render('$elapsed\u00b5s')}';
// Truncate to fit panel width (leaving room for borders + padding).
final maxLen = panelWidth - 4;
if (Style.visibleLength(line) > maxLen && maxLen > 3) {
content.writeln(
'${_truncateVisible(line, maxLen - 3)}${dim.render('...')}',
);
} else {
content.writeln(line);
}
}
if (messageEntries.length > maxDisplayMessages) {
content.writeln(
dim.render('(+${messageEntries.length - maxDisplayMessages} more)'),
);
}
}
}
// --- Output section ---
if (mode == DebugOverlayMode.output || mode == DebugOverlayMode.all) {
if (content.isNotEmpty) content.writeln();
if (mode == DebugOverlayMode.all) {
content.writeln(dim.render('── Output ──'));
}
if (outputEntries.isEmpty) {
content.writeln(dim.render('(no output)'));
} else {
final display = outputEntries.take(maxDisplayOutput);
for (final entry in display) {
final prefix = entry.source.name == 'stderr'
? Style().foreground(Colors.red).render('err')
: '';
final lineText = prefix.isNotEmpty
? '$prefix ${entry.line}'
: entry.line;
final maxLen = panelWidth - 4;
if (Style.visibleLength(lineText) > maxLen && maxLen > 3) {
content.writeln(
'${_truncateVisible(lineText, maxLen - 3)}${dim.render('...')}',
);
} else {
content.writeln(lineText);
}
}
if (outputEntries.length > maxDisplayOutput) {
content.writeln(
dim.render('(+${outputEntries.length - maxDisplayOutput} more)'),
);
}
}
}
// Determine panel title based on mode.
final panelTitle = switch (mode) {
DebugOverlayMode.metrics => title,
DebugOverlayMode.messages => 'Messages',
DebugOverlayMode.output => 'Captured Output',
DebugOverlayMode.all => 'Debug',
};
final rendered = PanelComponent(
title: panelTitle,
content: content.toString().trimRight(),
width: panelWidth,
renderConfig: RenderConfig(
terminalWidth: terminalWidthOverride ?? terminalWidth,
),
).render();
// Cache the result.
_cachedPanel = rendered;
_cachedCacheKey = cacheKey;
final lines = rendered.split('\n');
_cachedPanelHeight = lines.length;
return rendered;
}