diff --git a/AGENTS.md b/AGENTS.md index d3f5fca..3c1df9c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ The implementation is split into focused modules (files) under the `MarcerGameDv - MarcerGameDvdLauncher/ProgramHelpers.cs: Small shared helpers (resolve relative paths, centralized console message helper) used across modules. - MarcerGameDvdLauncher/OverlayDirectoryBrowser.cs: Filesystem overlay and browsing logic — merges root and patch directories, enumerates folders and ZIPs, protects against path traversal and ensures navigation cannot leave the configured roots. - MarcerGameDvdLauncher/NavigationController.cs: Encapsulates selection, scrolling and relative-path navigation logic (cursor, page up/down, per-directory remembered selection/state). -- MarcerGameDvdLauncher/MenuRenderer.cs: Console rendering logic — efficient per-line redraw, double-buffering and color selection according to overlay rules. +- MarcerGameDvdLauncher/MenuRenderer.cs: Console rendering logic — efficient per-line redraw, double-buffering, color selection according to overlay rules, and the help box overlay. - MarcerGameDvdLauncher/HatariLauncher.cs: Responsible for validating the Hatari executable and starting Hatari with the configured argument template (replaces `{cfg}` and `{zip}`). - MarcerGameDvdLauncher/UIErrorService.cs: Centralized UI error presentation using the console message helper. @@ -57,6 +57,7 @@ The console launcher is meant for browsing a games directory and can launch ZIP - Backspace: jump to parent directory (never outside root) - ESC: exit the program - PageUp/PageDown: jump by one page up/down through the file list + - `?`: show a help box with key bindings - The file list always shows exactly as many lines as fit the screen – ALWAYS **one line less** than the console height (`Console.WindowHeight - 1`). This avoids overflow at the bottom and ensures the selection never enters the non-visible area. Rationale: writing to the very last console line can cause the Windows console to auto-scroll or produce visual jumps when the cursor reaches the bottom row. Reserving one line prevents unintended scrolling/flicker and keeps the selection cursor strictly within the visible area. Maintenance: when changing rendering or navigation logic, always compute the displayed page size as `availableLines = Console.WindowHeight - 1` and keep this value consistent across MenuRenderer, NavigationController and any other code that references the console height. diff --git a/MarcerGameDvdLauncher/LauncherApp.cs b/MarcerGameDvdLauncher/LauncherApp.cs index dfaed38..8fd3922 100644 --- a/MarcerGameDvdLauncher/LauncherApp.cs +++ b/MarcerGameDvdLauncher/LauncherApp.cs @@ -142,6 +142,15 @@ namespace MarcerGameDvdLauncher } var key = Console.ReadKey(intercept: true); + if (key.KeyChar == '?') + { + _menuRenderer.ShowHelpBox(currentAvailableLines); + Console.ReadKey(intercept: true); + _menuRenderer.InvalidateCache(); + _menuRenderer.DrawMenu(_gameEntries, _navigationController.ScrollOffset, _navigationController.SelectedIndex, currentAvailableLines, isFav); + ProgramHelpers.FlushInputBuffer(); + continue; + } switch (key.Key) { case ConsoleKey.UpArrow: diff --git a/MarcerGameDvdLauncher/MenuRenderer.cs b/MarcerGameDvdLauncher/MenuRenderer.cs index e72147b..4e5284d 100644 --- a/MarcerGameDvdLauncher/MenuRenderer.cs +++ b/MarcerGameDvdLauncher/MenuRenderer.cs @@ -139,6 +139,93 @@ namespace MarcerGameDvdLauncher } } + // Invalidates the internal line cache so the next DrawMenu call + // performs a full redraw of every line. Useful after an overlay + // (e.g. help box) has overwritten the console directly. + public void InvalidateCache() + { + for (int i = 0; i < _cachedBuffer.Length; i++) + _cachedBuffer[i].Text = null!; + } + + // Renders a centered, bordered help box with key bindings inside the + // available console area. The caller is responsible for waiting on a + // key and redrawing the menu afterwards. + public void ShowHelpBox(int availableLines) + { + try + { + int width = Console.WindowWidth; + string[] helpLines = GetHelpLines(); + int boxHeight = Math.Min(helpLines.Length + 2, Math.Max(3, availableLines)); + int boxWidth = Math.Max(1, width); + int topRow = Math.Max(0, (availableLines - boxHeight) / 2); + + Console.BackgroundColor = ConsoleColor.DarkGray; + Console.ForegroundColor = ConsoleColor.White; + + string topBorder = "+" + new string('-', Math.Max(0, boxWidth - 2)) + "+"; + Console.SetCursorPosition(0, topRow); + Console.Write(topBorder); + + for (int i = 0; i < boxHeight - 2; i++) + { + int row = topRow + 1 + i; + string content; + if (i < helpLines.Length) + { + content = PadToWidth(helpLines[i], boxWidth - 2); + } + else + { + content = new string(' ', Math.Max(0, boxWidth - 2)); + } + Console.SetCursorPosition(0, row); + Console.Write("|" + content + "|"); + } + + int bottomRow = topRow + boxHeight - 1; + if (bottomRow < Console.WindowHeight) + { + string bottomBorder = "+" + new string('-', Math.Max(0, boxWidth - 2)) + "+"; + Console.SetCursorPosition(0, bottomRow); + Console.Write(bottomBorder); + } + + Console.ResetColor(); + } + catch + { + } + } + + private static string[] GetHelpLines() + { + return [ + " Help — Key Bindings", + " ", + " ↑ / ↓ Move selection up / down", + " Enter / → Open folder / launch ZIP with Hatari", + " ← / BS Go up one directory (never exceeds root)", + " ESC / Q Exit the program", + " PgUp Jump one page up", + " PgDn Jump one page down", + " * Toggle favorite on selected ZIP", + " ? Show this help", + " ", + " Navigation is strictly limited to RootDirectory.", + " The overlay shows both root and patch layers combined.", + " ", + " Press any key to continue...", + ]; + } + + private static string PadToWidth(string text, int width) + { + if (text.Length > width) return text.Substring(0, width); + return text + new string(' ', width - text.Length); + } + // Returns foreground and background colors for an entry depending on selection state private (ConsoleColor fg, ConsoleColor bg) GetColors(GameEntry e, bool selected) { diff --git a/README.md b/README.md index c29d2fa..e8d4d5a 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ A performant, consistent console launcher for the Hatari emulator on Windows. Co - ESC or Q: exit the program immediately - PageUp/PageDown: jump exactly one screen full (window height - 1) - `*`: toggle favorite on selected ZIP + - `?`: show a help box with key bindings - Display always one line less than console height; no overflow/cut-off - **Cursor position saving per directory:** - The last position/selection of each directory is retained, even after Backspace