fix stats formatting 3 on export option

This commit is contained in:
2026-05-20 12:58:59 +02:00
parent 4ea3c01887
commit 3442f7f289
2 changed files with 124 additions and 119 deletions
Binary file not shown.
+124 -119
View File
@@ -10,133 +10,138 @@ namespace HanaTui.Tui.Components;
/// </summary>
public static class StatsPanel
{
public static IRenderable Build(SystemSnapshot snap, TimeSpan elapsed, int panelWidth)
public static IRenderable Build(SystemSnapshot snap, TimeSpan elapsed, int panelWidth)
{
var barWidth = Math.Max(10, panelWidth - 16);
var grid = new Grid();
grid.AddColumn(new GridColumn().NoWrap());
// --- CPU ---
grid.AddRow(new Markup("[bold yellow]CPU[/]"));
if (snap.CpuPercents.Length == 0)
{
var barWidth = Math.Max(10, panelWidth - 16);
var grid = new Grid();
grid.AddColumn(new GridColumn().NoWrap());
// --- CPU ---
grid.AddRow(new Markup("[bold yellow]CPU[/]"));
if (snap.CpuPercents.Length == 0)
{
grid.AddRow(new Markup("[grey]No data[/]"));
}
else
{
for (int i = 0; i < snap.CpuPercents.Length; i++)
{
var label = snap.CpuLabels.Length > i ? snap.CpuLabels[i] : $"cpu{i}";
var displayLabel = label == "cpu" ? "Total" : label.Replace("cpu", "Core");
var pct = snap.CpuPercents[i];
// Build as Columns: label | bar | pct — no interpolation of dynamic values into markup
grid.AddRow(BuildBarRow(displayLabel, pct, barWidth));
}
}
grid.AddRow(new Text(""));
// --- Memory ---
grid.AddRow(new Markup("[bold cyan]MEMORY[/]"));
if (snap.MemTotalKb > 0)
{
var memPct = snap.MemUsedPercent;
grid.AddRow(BuildBarRow("Used", memPct, barWidth));
grid.AddRow(new Text($" {SystemSnapshot.FormatGb(snap.MemUsedKb)} / {SystemSnapshot.FormatGb(snap.MemTotalKb)}",
new Style(Color.Grey)));
}
else
{
grid.AddRow(new Markup("[grey]No data[/]"));
}
grid.AddRow(new Text(""));
// --- Swap ---
grid.AddRow(new Markup("[bold cyan]SWAP[/]"));
if (snap.SwapTotalKb > 0)
{
var swapPct = snap.SwapUsedPercent;
grid.AddRow(BuildBarRow("Used", swapPct, barWidth));
grid.AddRow(new Text($" {SystemSnapshot.FormatGb(snap.SwapUsedKb)} / {SystemSnapshot.FormatGb(snap.SwapTotalKb)}",
new Style(Color.Grey)));
}
else
{
grid.AddRow(new Markup("[grey]No swap[/]"));
}
grid.AddRow(new Text(""));
// --- Elapsed ---
// Two separate renderables composed — no interpolation of elapsed into markup
var elapsedGrid = new Grid();
elapsedGrid.AddColumn(new GridColumn().NoWrap());
elapsedGrid.AddColumn(new GridColumn().NoWrap());
elapsedGrid.AddRow(
new Markup("[bold]Elapsed:[/]"),
new Text(" " + FormatElapsed(elapsed), new Style(Color.Yellow)));
grid.AddRow(elapsedGrid);
return new Panel(grid)
{
Header = new PanelHeader("[bold] SYSTEM STATS [/]"),
Border = BoxBorder.Rounded,
Padding = new Padding(1, 0),
};
grid.AddRow(new Markup("[grey]No data[/]"));
}
else
{
for (int i = 0; i < snap.CpuPercents.Length; i++)
{
var label = snap.CpuLabels.Length > i ? snap.CpuLabels[i] : $"cpu{i}";
var displayLabel = label == "cpu" ? "Total" : label.Replace("cpu", "Core");
var pct = snap.CpuPercents[i];
// Build as Columns: label | bar | pct — no interpolation of dynamic values into markup
grid.AddRow(BuildBarRow(displayLabel, pct, barWidth));
}
}
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
grid.AddRow(new Text(""));
/// <summary>
/// Builds a single bar row as a Grid with three columns:
/// label (dim) | bar (block chars, plain Text with color) | pct (bold)
/// Nothing here goes through markup parsing with dynamic content.
/// </summary>
private static IRenderable BuildBarRow(string label, double pct, int barWidth)
// --- Memory ---
grid.AddRow(new Markup("[bold cyan]MEMORY[/]"));
if (snap.MemTotalKb > 0)
{
var filled = (int)Math.Round(pct / 100.0 * barWidth);
filled = Math.Clamp(filled, 0, barWidth);
var empty = barWidth - filled;
var filledColor = CpuColor(pct);
var filledStr = new string('\u2588', filled);
var emptyStr = new string('\u2591', empty);
var row = new Grid();
row.AddColumn(new GridColumn().NoWrap());
var colorMarkup = filledColor.ToMarkup();
// Construct a seamless markup string
var barMarkup = $"[grey][[[/][{colorMarkup}]{filledStr}[/][grey dim]{emptyStr}[/][grey]]][/]";
row.AddRow(
new Text($" {label,-5}", new Style(Color.Grey, decoration: Decoration.Dim)),
new Markup(barMarkup), // Replace Columns with a single Markup object
new Text($" {pct,5:F1}%", new Style(Color.White, decoration: Decoration.Bold))
);
return row;
var memPct = snap.MemUsedPercent;
grid.AddRow(BuildBarRow("Used", memPct, barWidth));
grid.AddRow(new Text($" {SystemSnapshot.FormatGb(snap.MemUsedKb)} / {SystemSnapshot.FormatGb(snap.MemTotalKb)}",
new Style(Color.Grey)));
}
else
{
grid.AddRow(new Markup("[grey]No data[/]"));
}
private static Color CpuColor(double pct) => pct switch
grid.AddRow(new Text(""));
// --- Swap ---
grid.AddRow(new Markup("[bold cyan]SWAP[/]"));
if (snap.SwapTotalKb > 0)
{
> 90 => Color.Red,
> 70 => Color.Yellow,
> 40 => Color.Green,
_ => Color.Blue,
var swapPct = snap.SwapUsedPercent;
grid.AddRow(BuildBarRow("Used", swapPct, barWidth));
grid.AddRow(new Text($" {SystemSnapshot.FormatGb(snap.SwapUsedKb)} / {SystemSnapshot.FormatGb(snap.SwapTotalKb)}",
new Style(Color.Grey)));
}
else
{
grid.AddRow(new Markup("[grey]No swap[/]"));
}
grid.AddRow(new Text(""));
// --- Elapsed ---
// Two separate renderables composed — no interpolation of elapsed into markup
var elapsedGrid = new Grid();
elapsedGrid.AddColumn(new GridColumn().NoWrap());
elapsedGrid.AddColumn(new GridColumn().NoWrap());
elapsedGrid.AddRow(
new Markup("[bold]Elapsed:[/]"),
new Text(" " + FormatElapsed(elapsed), new Style(Color.Yellow)));
grid.AddRow(elapsedGrid);
return new Panel(grid)
{
Header = new PanelHeader("[bold] SYSTEM STATS [/]"),
Border = BoxBorder.Rounded,
Padding = new Padding(1, 0),
};
}
private static string FormatElapsed(TimeSpan t)
{
if (t.TotalHours >= 1)
return $"{(int)t.TotalHours}h {t.Minutes:D2}m {t.Seconds:D2}s";
if (t.TotalMinutes >= 1)
return $"{t.Minutes}m {t.Seconds:D2}s";
return $"{t.Seconds}s";
}
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
/// <summary>
/// Builds a single bar row as a Grid with three columns:
/// label (dim) | bar (block chars, plain Text with color) | pct (bold)
/// Nothing here goes through markup parsing with dynamic content.
/// </summary>
private static IRenderable BuildBarRow(string label, double pct, int barWidth)
{
var filled = (int)Math.Round(pct / 100.0 * barWidth);
filled = Math.Clamp(filled, 0, barWidth);
var empty = barWidth - filled;
var filledColor = CpuColor(pct);
var filledStr = new string('\u2588', filled);
var emptyStr = new string('\u2591', empty);
var row = new Grid();
// FIX: Add all three columns back!
row.AddColumn(new GridColumn().NoWrap().Width(9)); // Column 1: Label
row.AddColumn(new GridColumn().NoWrap()); // Column 2: Bar
row.AddColumn(new GridColumn().NoWrap().Width(7)); // Column 3: Pct
var colorMarkup = filledColor.ToMarkup();
// Construct a seamless markup string
var barMarkup = $"[grey][[[/][{colorMarkup}]{filledStr}[/][grey dim]{emptyStr}[/][grey]]][/]";
row.AddRow(
new Text($" {label,-5}", new Style(Color.Grey, decoration: Decoration.Dim)),
new Markup(barMarkup),
new Text($" {pct,5:F1}%", new Style(Color.White, decoration: Decoration.Bold))
);
return row;
}
private static Color CpuColor(double pct) => pct switch
{
> 90 => Color.Red,
> 70 => Color.Yellow,
> 40 => Color.Green,
_ => Color.Blue,
};
private static string FormatElapsed(TimeSpan t)
{
if (t.TotalHours >= 1)
return $"{(int)t.TotalHours}h {t.Minutes:D2}m {t.Seconds:D2}s";
if (t.TotalMinutes >= 1)
return $"{t.Minutes}m {t.Seconds:D2}s";
return $"{t.Seconds}s";
}
}