From fcd270861c5dce9c8784d72a3fa6ecbbc74c7f7d Mon Sep 17 00:00:00 2001 From: Samuel Bouchet Date: Mon, 16 Mar 2026 19:50:39 +0100 Subject: [PATCH] Eliminate flicker on arrow key navigation in inventory and menus Replace clear-then-write pattern with overwrite-in-place: move cursor up to start of rendered area and write new content directly over old lines. Only clear trailing lines if the new render is shorter. This removes the visible blank frame between clear and re-render. --- src/OpenTheBox.Web/WebGameHost.cs | 9 ++++++--- src/OpenTheBox.Web/WebTerminal.cs | 12 ++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/OpenTheBox.Web/WebGameHost.cs b/src/OpenTheBox.Web/WebGameHost.cs index db5a2e1..9e7fef3 100644 --- a/src/OpenTheBox.Web/WebGameHost.cs +++ b/src/OpenTheBox.Web/WebGameHost.cs @@ -615,12 +615,15 @@ public sealed class WebGameHost } string rendered = writer.ToString().Replace("\n", "\r\n"); - int renderedLines = rendered.Split('\n').Length; + int renderedLines = rendered.Count(c => c == '\n'); - // Clear previous render and write new one + // Overwrite in place: move cursor up, write new content over old lines, + // then clear any leftover lines if new render is shorter. No clear-before-write = no flicker. if (previousRenderedLines > 0) - await _terminal.WriteAsync($"\x1b[{previousRenderedLines}A\x1b[J"); + await _terminal.WriteAsync($"\x1b[{previousRenderedLines}A\r"); await _terminal.WriteAsync(rendered); + if (previousRenderedLines > renderedLines) + await _terminal.WriteAsync("\x1b[J"); previousRenderedLines = renderedLines; var key = await _terminal.ReadKeyAsync(); diff --git a/src/OpenTheBox.Web/WebTerminal.cs b/src/OpenTheBox.Web/WebTerminal.cs index 86c9757..e2ea27b 100644 --- a/src/OpenTheBox.Web/WebTerminal.cs +++ b/src/OpenTheBox.Web/WebTerminal.cs @@ -235,10 +235,10 @@ public sealed class WebTerminal { int selected = 0; int pageSize = Math.Min(10, options.Count); + int previousLineCount = 0; while (true) { - int scrollOffset = 0; if (selected >= pageSize) scrollOffset = selected - pageSize + 1; @@ -276,7 +276,14 @@ public sealed class WebTerminal string rendered = writer.ToString().Replace("\n", "\r\n"); int lineCount = rendered.Count(c => c == '\n'); + // Overwrite in place: move cursor up, write over old lines (no flicker). + // Only clear leftover lines if the new render is shorter than previous. + if (previousLineCount > 0) + await WriteAsync($"\x1b[{previousLineCount}A\r"); await WriteAsync(rendered); + if (previousLineCount > lineCount) + await WriteAsync("\x1b[J"); + previousLineCount = lineCount; var key = await ReadKeyAsync(); @@ -301,9 +308,6 @@ public sealed class WebTerminal case ConsoleKey.D8 or ConsoleKey.NumPad8: if (options.Count >= 8) { await WriteAsync($"\x1b[{lineCount}A\x1b[J"); return 7; } break; case ConsoleKey.D9 or ConsoleKey.NumPad9: if (options.Count >= 9) { await WriteAsync($"\x1b[{lineCount}A\x1b[J"); return 8; } break; } - - // Clear menu before re-rendering - await WriteAsync($"\x1b[{lineCount}A\x1b[J"); } }