Fix terminal height: fit addon now measures real container size

- Show terminal container BEFORE calling fitAddon.fit() so it can
  measure actual dimensions (was measuring display:none → 0 height)
- Read actual terminal rows from JS after init (getDimensions)
- Remove max-height:600px CSS constraint, let terminal fill viewport
- Revert preRender approach that caused flickering on arrow selection
- Height is now dynamic (instance property) instead of hardcoded 30
This commit is contained in:
Samuel Bouchet 2026-03-16 19:03:31 +01:00
parent 2c43e31605
commit a6eada7473
4 changed files with 35 additions and 19 deletions

View file

@ -592,7 +592,7 @@ public sealed class WebGameHost
ColorSystem = ColorSystemSupport.TrueColor ColorSystem = ColorSystemSupport.TrueColor
}); });
bufferConsole.Profile.Width = WebTerminal.Width; bufferConsole.Profile.Width = WebTerminal.Width;
bufferConsole.Profile.Height = WebTerminal.Height; bufferConsole.Profile.Height = _terminal.Height;
bufferConsole.Write(InventoryPanel.Render(_state, _registry, _loc, scrollOffset, selectedIndex: selectedIndex)); bufferConsole.Write(InventoryPanel.Render(_state, _registry, _loc, scrollOffset, selectedIndex: selectedIndex));

View file

@ -28,7 +28,7 @@ public sealed class WebTerminal
}); });
public const int Width = 120; public const int Width = 120;
public const int Height = 30; public int Height { get; private set; } = 30;
public WebTerminal(IJSRuntime js) public WebTerminal(IJSRuntime js)
{ {
@ -37,13 +37,24 @@ public sealed class WebTerminal
} }
/// <summary> /// <summary>
/// Initializes the xterm.js terminal in the browser. /// Initializes the xterm.js terminal in the browser and reads actual dimensions.
/// </summary> /// </summary>
public async Task InitAsync() public async Task InitAsync()
{ {
await _js.InvokeVoidAsync("terminalInterop.init"); await _js.InvokeVoidAsync("terminalInterop.init");
// Read actual terminal dimensions after fitAddon has sized the terminal
try
{
var dims = await _js.InvokeAsync<TerminalDimensions>("terminalInterop.getDimensions");
if (dims.rows >= 30)
Height = dims.rows;
}
catch { /* fallback to 30 */ }
} }
private record TerminalDimensions(int cols, int rows);
// ── Output ─────────────────────────────────────────────────────────── // ── Output ───────────────────────────────────────────────────────────
/// <summary> /// <summary>
@ -83,7 +94,7 @@ public sealed class WebTerminal
ColorSystem = ColorSystemSupport.TrueColor ColorSystem = ColorSystemSupport.TrueColor
}); });
console.Profile.Width = Width; console.Profile.Width = Width;
console.Profile.Height = Height; console.Profile.Height = _instance?.Height ?? 50;
console.Write(renderable); console.Write(renderable);
return writer.ToString(); return writer.ToString();
} }
@ -227,6 +238,7 @@ public sealed class WebTerminal
while (true) while (true)
{ {
int scrollOffset = 0; int scrollOffset = 0;
if (selected >= pageSize) if (selected >= pageSize)
scrollOffset = selected - pageSize + 1; scrollOffset = selected - pageSize + 1;
@ -262,7 +274,6 @@ public sealed class WebTerminal
console.MarkupLine("[dim] ▼ ...[/]"); console.MarkupLine("[dim] ▼ ...[/]");
string rendered = writer.ToString().Replace("\n", "\r\n"); string rendered = writer.ToString().Replace("\n", "\r\n");
// Count actual line breaks — Split('\n').Length overcounts by 1 due to trailing newline
int lineCount = rendered.Count(c => c == '\n'); int lineCount = rendered.Count(c => c == '\n');
await WriteAsync(rendered); await WriteAsync(rendered);
@ -278,7 +289,6 @@ public sealed class WebTerminal
selected = (selected + 1) % options.Count; selected = (selected + 1) % options.Count;
break; break;
case ConsoleKey.Enter: case ConsoleKey.Enter:
// Clear the rendered selection before returning
await WriteAsync($"\x1b[{lineCount}A\x1b[J"); await WriteAsync($"\x1b[{lineCount}A\x1b[J");
return selected; return selected;
case ConsoleKey.D1 or ConsoleKey.NumPad1: if (options.Count >= 1) { await WriteAsync($"\x1b[{lineCount}A\x1b[J"); return 0; } break; case ConsoleKey.D1 or ConsoleKey.NumPad1: if (options.Count >= 1) { await WriteAsync($"\x1b[{lineCount}A\x1b[J"); return 0; } break;
@ -292,7 +302,7 @@ public sealed class WebTerminal
case ConsoleKey.D9 or ConsoleKey.NumPad9: if (options.Count >= 9) { await WriteAsync($"\x1b[{lineCount}A\x1b[J"); return 8; } break; case ConsoleKey.D9 or ConsoleKey.NumPad9: if (options.Count >= 9) { await WriteAsync($"\x1b[{lineCount}A\x1b[J"); return 8; } break;
} }
// Clear and re-render // Clear menu before re-rendering
await WriteAsync($"\x1b[{lineCount}A\x1b[J"); await WriteAsync($"\x1b[{lineCount}A\x1b[J");
} }
} }

View file

@ -31,8 +31,7 @@ body {
#terminal { #terminal {
width: 100%; width: 100%;
height: 100%; height: 100%;
max-width: 960px; max-width: 1200px;
max-height: 600px;
} }
/* Loading screen */ /* Loading screen */

View file

@ -24,12 +24,17 @@ window.terminalInterop = {
const fitAddon = new FitAddon.FitAddon(); const fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon); term.loadAddon(fitAddon);
// Show terminal container BEFORE opening so fitAddon can measure real dimensions
document.getElementById('loading').classList.add('hidden');
document.getElementById('terminal-container').classList.add('active');
term.open(document.getElementById('terminal')); term.open(document.getElementById('terminal'));
// Fit to container, but enforce minimum dimensions for game readability // Fit to container, enforce minimum dimensions for game readability
fitAddon.fit(); fitAddon.fit();
if (term.cols < 120 || term.rows < 30) { const minCols = 120, minRows = 30;
term.resize(Math.max(term.cols, 120), Math.max(term.rows, 30)); if (term.cols < minCols || term.rows < minRows) {
term.resize(Math.max(term.cols, minCols), Math.max(term.rows, minRows));
} }
// Forward key input to C# // Forward key input to C#
@ -40,18 +45,13 @@ window.terminalInterop = {
// Handle resize // Handle resize
window.addEventListener('resize', function () { window.addEventListener('resize', function () {
fitAddon.fit(); fitAddon.fit();
if (term.cols < 120 || term.rows < 30) { if (term.cols < minCols || term.rows < minRows) {
term.resize(Math.max(term.cols, 120), Math.max(term.rows, 30)); term.resize(Math.max(term.cols, minCols), Math.max(term.rows, minRows));
} }
}); });
this.term = term; this.term = term;
this.fitAddon = fitAddon; this.fitAddon = fitAddon;
// Show terminal, hide loading
document.getElementById('loading').classList.add('hidden');
document.getElementById('terminal-container').classList.add('active');
term.focus(); term.focus();
}, },
@ -72,5 +72,12 @@ window.terminalInterop = {
if (this.term) { if (this.term) {
this.term.focus(); this.term.focus();
} }
},
getDimensions: function () {
if (this.term) {
return { cols: this.term.cols, rows: this.term.rows };
}
return { cols: 120, rows: 30 };
} }
}; };