11 Commits
Author SHA1 Message Date
stefankoelle 7d9a73ebf6 moonweb.yml 2026-08-14 23:54:35 +02:00
stefankoelle 0cc389ff5d demo.sh update 2026-08-11 21:14:06 +02:00
stefankoelle 305588da8f help box sorted 2026-08-11 21:07:08 +02:00
stefankoelle bc39039c3c help box updated 2026-08-11 21:05:19 +02:00
stefankoelle d5d7cfb7c4 Fix help screen display
- Use symbols for cursor arrows (↑ / ↓, →, ←)
- Combine ESC/q into single line
- Simplify help box to grey background only
2026-08-11 21:02:53 +02:00
stefankoelle 12586262cb help box 2026-08-11 21:00:46 +02:00
stefankoelle 9c65dafdcf release v0.9.7
- Update AGENTS.md: cleanup redundant policy content
- Clean BUGS.md: remove all resolved bugs/issues
- Delete PLAN.md: completed document
- Fix InputController.cs: redraw menu after Hatari with cache invalidation
- ProgramHelpers.cs: add convenience ShowConsoleMessage overload
2026-08-11 20:44:33 +02:00
stefankoelle 03f7e5eed4 hatari running message redraw screen 2026-08-11 20:21:21 +02:00
stefankoelle 6e32223eef AGENTS.md 2026-08-11 20:17:53 +02:00
stefankoelle 2bb2b09c1d remove PLAN.md 2026-08-11 20:12:40 +02:00
stefankoelle bc9bb8e862 fix bugs 2026-08-11 20:11:23 +02:00
8 changed files with 60 additions and 180 deletions
+6
View File
@@ -0,0 +1,6 @@
title: "Marcer GameDVD Launcher"
emoji: "🕹️"
category: code
subcategory: "Retro"
status: active
stack: [C#, .NET, Console]
+5 -16
View File
@@ -14,7 +14,7 @@ The implementation is split into focused modules (files) under the `src/MarcerGa
- MarcerGameDvdLauncher/NavigationController.cs: Encapsulates selection, scrolling and relative-path navigation logic (cursor, page up/down, per-directory remembered selection/state); uses named scroll-fraction constants.
- MarcerGameDvdLauncher/MenuRenderer.cs: Console rendering logic — efficient per-line redraw, double-buffering, configurable color selection via injected `AppColorConfig`, and the help box overlay.
- MarcerGameDvdLauncher/InputController.cs: Handles key events (arrow keys, Enter, Backspace, PageUp/Down, `*`, `?`, ESC, RightArrow) and the associated navigation/drawing logic; owns `ReloadGameEntries` and the virtual `Favorites` folder integration.
- MarcerGameDvdLauncher/HatariLauncher.cs: Responsible for validating the Hatari executable and starting Hatari with the configured argument template (replaces `{cfg}` and `{zip}`). Exposes `DefaultConfigFile` constant (`MarcerGameDvd-Hatari.cfg`); when `Hatari.ConfigFile` is empty in `launcher.config.json`, the bundled config from the executable directory is used automatically.
- MarcerGameDvdLauncher/HatariLauncher.cs: Responsible for validating the Hatari executable and starting Hatari with the configured argument template (replaces `{cfg}` and `{zip}`). Exposes `DefaultConfigFile` constant (`MarcerGameDvd-Hatari.cfg`).
- MarcerGameDvdLauncher/FavoritesService.cs: Manages the favorites/bookmark system — toggling favorites on ZIPs, persisting them to `favorites.txt` (via `DefaultFileName` constant), and providing the virtual `Favorites` folder view (via `FavoritesRootName` constant).
- MarcerGameDvdLauncher/UIErrorService.cs: Centralized UI error presentation using the console message helper.
@@ -32,15 +32,6 @@ The Launcher cannot be executed or tested via `scripts/start.cmd` from this envi
4. The GitHub Action handles the rest (build, ZIP, release notes, GitHub Release).
- Local release artifacts in `release/` are optional and no longer required for the release process.
Additional policy:
- README.md must be written in English. Any functional change that affects usage, configuration, or behavior MUST update README.md in English immediately after the change. If there are consequential changes to developer-facing policies, build steps, or requirements, AGENTS.md must be updated as well.
Developer note: Visual Studio Solution
- A Visual Studio solution file exists at `src/marcer-gamedvd-launcher.sln`. Developers may open this solution in Visual Studio to work on the project, debug and build from the IDE. The solution references `MarcerGameDvdLauncher\MarcerGameDvdLauncher.csproj` and includes Debug and Release configurations. Use `scripts/build.cmd` (Windows) or `scripts/build.sh` (Linux/macOS) and `scripts/start.cmd` (Windows) or `scripts/start.sh` (Linux/macOS) for consistent command-line builds/releases as described elsewhere in this document.
With this, it is ensured that binary/release files never end up in git, and the release process is always traceable and performed exclusively manually in the web interface.
# Requirements for the Marcer GameDVD Launcher (AGENTS.md)
## Basic Function / Purpose
@@ -55,8 +46,8 @@ The console launcher is meant for browsing a games directory and can launch ZIP
- Only display subfolders and ZIP files; ignore other file types and hidden files.
- Navigation and control exclusively with these keys:
- Arrow down/up: scroll by single entries
- Enter: open folder or launch ZIP with Hatari
- Backspace: jump to parent directory (never outside root)
- Enter / RightArrow: open folder or launch ZIP with Hatari
- Backspace / LeftArrow: 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
@@ -80,7 +71,6 @@ The console launcher is meant for browsing a games directory and can launch ZIP
- In the root directory, Backspace must have no effect (no error, do not leave the program).
- Navigation (Backspace, Enter, etc.) must remain robust even for very deep or large directory trees.
- Hatari.Executable is validated during startup: the path is resolved (relative to the EXE directory when applicable) and must point to an existing file. If validation fails the program must present a clear error and exit.
- `Hatari.ConfigFile` (if provided) is resolved relative to the EXE directory and validated. If empty, the bundled `MarcerGameDvd-Hatari.cfg` is used automatically.
### Miscellaneous
- Build and start scripts (`scripts/build.cmd` / `scripts/build.sh` / `scripts/start.cmd` / `scripts/start.sh`) are present and must be used.
@@ -88,9 +78,8 @@ The console launcher is meant for browsing a games directory and can launch ZIP
- After making any code changes that affect behavior or touch source files, run the platform build script (`scripts/build.cmd` on Windows, `scripts/build.sh` on Linux/macOS) and ensure the build completes successfully before committing. Additionally, perform a manual functional test on a Windows, Linux, or macOS machine prior to pushing a release.
- The console window can have any number of lines; display/navigation must adapt dynamically.
- For every release, a Release Notes file must be maintained that summarizes all changes, bugfixes, and new features in that version; Release Notes must be provided with the release asset.
- **IMPORTANT:** With any functional change to the launcher, BOTH this file (AGENTS.md) AND the README.md must always be updated and kept current. Immediately after, a successful build must be executed. This is mandatory for all development on the project.
- README.md must be written in English. Any functional change that affects usage, configuration, or behavior MUST update README.md in English immediately after the change. If there are consequential changes to developer-facing policies, build steps, or requirements, AGENTS.md must be updated as well.
- **IMPORTANT:** With any functional change to the launcher, BOTH this file (AGENTS.md) AND the README.md must always be updated and kept current. Immediately after, a successful build must be executed. This is mandatory for all development on the project.
---
+12 -46
View File
@@ -2,56 +2,19 @@
## Critical Bugs
### 1. ArgsTemplate `{cfg}` placeholder not handled when ConfigFile is empty
**Location**: `HatariLauncher.cs:36`
**Problem**: If `Hatari.ConfigFile` is empty string (allowed per config) but `ArgsTemplate` contains `{cfg}`, the replacement produces `-c "" --disk-a "path"` which Hatari may reject.
**Fix**: Conditionally remove `-c "{cfg}"` when `_cfgPath` is empty, or validate template matches config.
### 2. `MenuRenderer.RedrawEntry()` potential crash on console resize
**Location**: `MenuRenderer.cs:67-98`
**Problem**: If console resizes smaller since last draw, `row` parameter may exceed new `availableLines`. The `else` branch writes directly via `WriteConsoleLine` without bounds checking against actual console height, risking `ArgumentOutOfRangeException` on `Console.SetCursorPosition`.
**Fix**: Validate `row < Console.WindowHeight` before writing, or clamp to valid range.
_No open critical bugs._
## Medium Bugs
### 3. `HatariLauncher` constructor doesn't validate `ArgsTemplate`
**Location**: `HatariLauncher.cs:12-24`
**Problem**: Constructor validates `exePath` existence but allows empty/null `argsTemplate`. `Launch()` will fail at runtime with empty string replace. Defense-in-depth validation missing.
**Fix**: Add `if (string.IsNullOrWhiteSpace(argsTemplate) || !argsTemplate.Contains("{zip}")) throw ...;`
### 4. ZIP launch doesn't validate file exists on disk
**Location**: `InputController.cs:185-196`
**Problem**: `PatchPath` or `RootPath` used directly without checking `File.Exists()`. Overlay logic should prevent this, but no defense-in-depth.
**Fix**: Validate `File.Exists(zipToLaunch)` before calling `_hatariLauncher.Launch()`.
_No open medium bugs._
## Documentation Inconsistencies
### 5. `Hatari.ConfigFile` required status unclear
**Location**: `README.md:96` vs `LauncherApp.cs:67-68`
**Conflict**: README marks `Hatari.ConfigFile` as required (✅). Code allows empty string and only validates if non-empty. If template uses `{cfg}` but ConfigFile empty → broken args.
**Resolution**: ConfigFile is now optional. When empty, the bundled `MarcerGameDvd-Hatari.cfg` (shipped in the release) is used automatically. README updated to reflect this. ✅ Fixed
### 6. `RightArrow` key binding missing from AGENTS.md requirements
**Location**: `AGENTS.md:57-58` vs `InputController.cs:172-173`
**Conflict**: AGENTS.md lists only Arrow up/down, Enter, Backspace, ESC, PageUp/Down, `?`. Code also handles `ConsoleKey.RightArrow` as alias for Enter (open folder/launch ZIP). README correctly documents it.
**Resolution**: Update AGENTS.md to include RightArrow.
### 7. `ShowModalUntilReturnReleased` race condition on resize
**Location**: `ProgramHelpers.cs:62-98`
**Problem**: Captures `lastRow = AvailableLines` at start. If console resizes during the modal wait loop, the clear writes to stale row position.
**Fix**: Re-read `AvailableLines` before clearing, or lock console during modal (not easily possible).
_No open documentation inconsistencies._
## Code Quality / Minor
### 8. `ProgramHelpers.ShowConsoleMessage` signature could use `params`
**Location**: `ProgramHelpers.cs:110`
**Current**: `public static void ShowConsoleMessage(string[] lines, ...)`
**Called as**: `ShowConsoleMessage([...], ...)` (collection expression)
**Suggestion**: Change to `params string[] lines` for more idiomatic usage.
### 9. `OverlayDirectoryBrowser` patch path computation order
**Location**: `OverlayDirectoryBrowser.cs:47-50`
**Observation**: Path traversal protection (`IsSubPathOf`) runs after `Path.Combine` but before `Directory.Exists`. Logic appears correct, but worth verifying with `rel` containing `..` when patch layer enabled.
_No open code quality issues._
---
@@ -68,13 +31,16 @@
| `Hatari.ConfigFile` optional with bundled fallback | ✅ Done | Empty ConfigFile falls back to bundled `MarcerGameDvd-Hatari.cfg` |
| `FavoritesService.Save()` swallow comment | ✅ Done | Explains intentional behavior |
| `UIErrorService` no-rethrow doc | ✅ Done | Explains design decision |
| ArgsTemplate `{cfg}` empty ConfigFile fallback | ✅ Done | `HatariLauncher.Launch()` falls back to bundled config |
| `MenuRenderer.RedrawEntry()` resize bounds check | ✅ Done | Row validated against `availableLines` + `Console.WindowHeight`, catch in `WriteConsoleLine` |
| `HatariLauncher` ArgsTemplate validation | ✅ Done | Constructor validates both `{zip}` and `{cfg}` placeholders |
| ZIP launch file existence check | ✅ Done | `File.Exists` defense-in-depth in `InputController` |
| `RightArrow`/`LeftArrow` in AGENTS.md | ✅ Done | Added to key bindings |
| `OverlayDirectoryBrowser` path traversal | ✅ Non-issue | `..` segments filtered at entry, `IsSubPathOf` as additional safety net |
| `ShowConsoleMessage` params overload | ✅ Done | Added convenience overload with flat argument list |
---
## Priority Recommendation
1. **Fix #1 (ArgsTemplate `{cfg}`)** - ✅ Fixed: empty ConfigFile now falls back to bundled config
2. **Fix #2 (RedrawEntry bounds)** - Potential crash on resize
3. **Fix #3 (HatariLauncher validation)** - ✅ Done (per BUGS.md Fixed table)
4. **Fix #4 (ZIP path validation)** - User-facing robustness
5. **Sync #5 & #6 (docs)** - ✅ Done: ConfigFile documented as optional with fallback
All identified issues have been resolved. See the Fixed table above for details.
-89
View File
@@ -1,89 +0,0 @@
# PLAN
Ziel: Das Repo aufräumen (Doku, Code, Config)
## 1. Code-Qualität Schwächen beheben (harmlos, rein refaktoriell)
- [x] **LauncherApp.cs zerlegen:** Die ~300-Zeilen-Klasse in separate Klassen aufteilen
(InputController.cs extrahiert mit Key-Handling + ReloadGameEntries; Lifecycle & Main-Loop bleiben in AppHost).
Ziel: keine Funktionsänderung, nur Struktur.
- [x] **Magische Strings entfernen / zentralisieren (nur im Code, siehe 2c):**
- [x] Virtueller Ordnername `"Favorites"``FavoritesService.FavoritesRootName` Konstante (nicht konfigurierbar).
- [x] Dateiname `"favorites.txt"``FavoritesService.DefaultFileName` Konstante (nicht konfigurierbar).
- [x] Console-Titel `"Marcer GameDVD Launcher"` (Program.cs) → `DefaultTitle` Konstante (nicht konfigurierbar).
- [x] Default-ArgsTemplate `-c "{cfg}" --disk-a "{zip}"` (LauncherApp.cs) → redundanten Fallback entfernt; Config liefert das Template (Validation `{zip}` existiert schon).
- [x] **Scroll-Trigger magische Zahlen** (NavigationController.cs: `2/3`, `1/3`) in benannte Konstanten
(`BottomScrollFraction = 2.0/3.0`, `TopScrollFraction = 1.0/3.0`) mit Kommentar aufzählen.
- [x] Redundanz beseitigen: `RedrawEntry` — überflüssige `maxRow`-Logik entfernt, `EnsureCacheForRow` durch `EnsureCache` ersetzt (klare Formulierung).
- [x] **Fehler-Schlucken besprechen:** `FavoritesService.Save()` (catch leer) — Kommentar korrigiert ("bewusst still", erklärt warum Persistence-Fehler nicht zum Absturz führen); `UIErrorService` — DocComment erweitert (Fehler werden gezeigt, nicht regeworfen, App bleibt im Loop).
- [ ] (Optional) Testprojekt hinzufügen für NavigationController & OverlayDirectoryBrowser
vorab mit Nutzer klären, da Policy bisher keine Tests vorsieht.
---
## 2. Hardcoded-Werte Aufgaben
### 2a. Colorschema konfigurierbar machen (entschieden)
- Aktuell: `MenuRenderer.cs` (`GetColorForEntry`) und README/agents-Doku (6 Entry-Typen +
Auswahl-Schema schwarz/blau).
- Ziel: Farben (Foreground pro Entry-Typ + Selection-Farben) in `launcher.config.json` konfigurierbar
machen (**Entscheidung: ja, konfigurierbar**), mit Default-Fallback auf heutige Werte.
- [x] `AppConfig` erweitern: neuen Abschnitt `"Colors"` hinzugefügt:
- [x] POCO `AppColorConfig` mit `ConsoleColor`-Werten (als String, z.B. `"Yellow"`):
FolderBoth, FolderPatchOnly, FolderRootOnly, ZipBoth, ZipRootOnly, ZipPatchOnly,
SelectedForeground, SelectedBackground, VirtualEntry.
- [x] Deserialisierung per `Enum.TryParse<ConsoleColor>` + Default-Fallback (manuell via `ParseAppColors`/`ParseColorField`).
- [x] `MenuRenderer` bekommt `AppColorConfig` via Konstruktor-Injection; `GetColorForEntry`/`GetColors` nutzen Config statt Konstanten.
- [x] Falls Config-Werte fehlen → heutiges Verhalten beibehalten (fallback).
- [x] Doku synchronisiert: README + AGENTS.md Farbtabellen auf Config-Felder verlinkt.
- [x] Beispielwerte ins `launcher.config.example.json` aufgenommen.
### 2b. Benutzer-Config (User-Config) entschieden
Ziel: die echte lokale Config gehört NICHT ins Repo, sondern bleibt lokal.
- [x] **Entscheidung getroffen (Variante 1):** `launcher.config.json` bleibt im Build-Output/EXE-Ordner (lokal, gitignored). Die `launcher.config.example.json` wird beim Release mitgeliefert und der Nutzer kopiert sie manuell zu `launcher.config.json`.
- [x] KEINE zusätzliche Suchreihenfolge (`%APPDATA%`, `~/.config`) implementiert — Pfad ist ausschließlich `AppContext.BaseDirectory`.
- [x] KEIN CLI-Parameter `--config` eingeführt.
- [x] `.gitignore` ergänzt: `launcher.config.json` und `favorites.txt` werden nie committet.
- [x] README: Abschnitt „Configuration" beschreibt manuelles Kopieren der example.
### 2c. Platzhalterwerte zentralisieren (sehr detailliert)
**Was bedeutet das Problem?**
Einige Werte stehen direkt („hartkodiert") im Code statt an einer zentralen Stelle. Bei Wiederverwendung
oder Änderung des Verhaltens sucht man sie an mehreren Stellen im Quelltext — und es passieren schnell
Inkonsistenzen. Die folgenden Werte sind betroffen:
| # | Wert | Aktuelle Stelle | Was es tut | Geplanter Fix |
|---|------|-----------------|------------|---------------|
| 1 | `"Favorites"` | `LauncherApp.cs` (2×) | Virtueller Ordnername, der dem Nutzer als Eintrag an der Wurzel angezeigt wird. Wenn zwei voneinander abweichende Literale im Codestand bleiben, funktioniert Enter/Anzeige dieses Ordners nicht mehr. | Eine einzige Konstante `const string FavoritesRootName = "Favorites";` (z.B. in `FavoritesService`); beide Verwendungen darauf zurückführen. **Nur Code-Konstante, nicht konfigurierbar.** |
| 2 | `"favorites.txt"` | `LauncherApp.cs` | Dateiname der Favoriten-Persistenz (liegt im Patch-Dir oder neben EXE). | Konstante im `FavoritesService` (z.B. `public const string DefaultFileName = "favorites.txt";`); Aufrufer nutzen diese statt Literal. **Nur Code-Konstante, nicht konfigurierbar.** |
| 3 | `"Marcer GameDVD Launcher"` | `Program.cs` (`Console.Title`) | Konsolen-Titel, der im Fenstertitel erscheint. | Konstante `const string DefaultTitle = "Marcer GameDVD Launcher";` (Nutzer-visible, aber **nicht konfigurierbar** — fixer Anzeige-Titel). |
| 4 | Default-ArgsTemplate `-c "{cfg}" --disk-a "{zip}"` | `LauncherApp.cs` | Redundanter Fallback, falls `Hatari.ArgsTemplate` in der Config nicht gesetzt ist. Ist bereits in der example definiert → das Hardcode ist nur ein Sicherheitsnetz. | Redundanz entfernen: Fallback streichen und erzwingen, dass die Config den Template liefert (Validation `{zip}` existiert schon). **Config-Feld bleibt die einzige Quelle.** |
| 5 | Scroll-Anteile `2/3` und `1/3` | `NavigationController.cs` | Bestimmt, ab welcher relativen Position im sichtbaren Fenster automatisch gescrollt wird (Cursor bei 2/3 unten → scrollen, bei 1/3 oben → zurückscrollen). | Benannte Konstanten `BottomScrollTriggerFraction = 2f/3f` und `TopScrollTriggerFraction = 1f/3f` mit Kommentar; Logik bleibt identisch. **Nur Code-Konstanten, nicht konfigurierbar.** |
| 6 | `WindowHeight - 1` / `WindowWidth` | `LauncherApp.cs` / `MenuRenderer.cs` | Die Liste ist immer eine Zeile weniger als die Konsolenhöhe (Policy, verhindert Auto-Scroll am Fensterrand). Wird an mehreren Stellen erneut berechnet. | Zentrale Helfer-Berechnung (z.B. statische Methode/Property `AvailableLines`), damit die Policy an genau einer Stelle kodiert ist; Logik unverändert (Resize-Handling bleibt). **Nur Code, nicht konfigurierbar.** |
| 7 | `STD_INPUT_HANDLE = -10`, `VK_RETURN = 0x0D` | `ProgramHelpers.cs` | Win32-Konstanten für FlushConsoleInputBuffer bzw. GetAsyncKeyState. Diese sind per Definition konstant. | KEINE Änderung nötig — nur per Kommentar als „bewusst hartkodiert" markieren (Win32-API-Konstanten). |
**Entscheidung:** Alle o.g. Werte werden **nur im Code zentralisiert** (Konstanten/Helfer).
Sie werden **NICHT** als Konfigurationsfelder in `launcher.config.json` angeboten — es gibt keine
externe Anpassungsmöglichkeit dafür.
**Verbindliche To-dos für 2c:**
- [x] Konstanten/Helfer eingeführt für: `FavoritesRootName` (FavoritesService), `DefaultFileName` (FavoritesService), `DefaultTitle` (Program), `BottomScrollFraction`/`TopScrollFraction` (NavigationController), `AvailableLines`-Helfer (ProgramHelpers).
- [x] Default-ArgsTemplate-Fallback entfernt; Config muss `ArgsTemplate` immer liefern (Validation existiert bereits).
- [x] Alle String-Literale auf Konstanten zurückgeführt (kein doppeltes `"Favorites"` mehr — nur die Konstantendefinition).
- [x] Kommentare ergänzt, die erklären, WARUM Werte fest sind (`AvailableLines` als Policy, Win32-Konstanten als API-konstant, Scroll-Anteile als feste Navigation).
- [x] Win32-Konstanten (Punkt 7) NICHT angefasst, nur per Kommentar als „bewusst hartkodiert" markiert.
- [x] README/agents nicht um diese rein implementativen Werte erweitert (keine Config-Felder dokumentiert);
Doku bleibt bei nutzersichtbaren, konfigurierbaren Werten (Farben, Titel, RootDir/Patch/Hatari).
---
## 4. Abschlusskriterien
- [x] Commit mit aussagekräftiger Message (nur echter Autor, kein Co-Author).
- [x] Nach Doku- und Code-Änderungen: `scripts/build.sh` (Linux) läuft fehlerfrei.
+1
View File
@@ -166,6 +166,7 @@ HATARI_FAKE="$DEMO_DIR/hatari.exe"
cat > "$HATARI_FAKE" <<'HATEXEC'
#!/bin/bash
echo "[DEMO] Hatari would launch with: $0 $@"
read -n 1 -r -p $'Press any key to continue... '
HATEXEC
chmod +x "$HATARI_FAKE"
+11 -1
View File
@@ -163,6 +163,13 @@ namespace MarcerGameDvdLauncher
if (!isDirectory && _gameEntries.Count > 0 && _gameEntries[navigationController.SelectedIndex].Kind == EntryKind.Zip)
{
string zipToLaunch = _gameEntries[navigationController.SelectedIndex].InPatch ? _gameEntries[navigationController.SelectedIndex].PatchPath : _gameEntries[navigationController.SelectedIndex].RootPath;
if (!File.Exists(zipToLaunch))
{
errorService.ShowError($"ZIP file not found: {zipToLaunch}");
DrawMenu(availableLines);
ProgramHelpers.FlushInputBuffer();
break;
}
try
{
hatariLauncher.Launch(zipToLaunch);
@@ -171,7 +178,10 @@ namespace MarcerGameDvdLauncher
{
errorService.ShowError(ex.Message);
}
// Redraw menu after Hatari has exited
// Redraw menu after Hatari has exited.
// Invalidate the cache because the modal overwrote
// a console row that the cache still considers valid.
menuRenderer.InvalidateCache();
DrawMenu(availableLines);
}
// flush input to avoid leftover key events after an enter/navigation
+19 -28
View File
@@ -133,7 +133,7 @@ namespace MarcerGameDvdLauncher
_cachedBuffer[i].Text = null!;
}
// Renders a centered, bordered help box with key bindings inside the
// Renders a centered help box with key bindings inside the
// available console area. The caller is responsible for waiting on a
// key and redrawing the menu afterward.
public void ShowHelpBox(int availableLines)
@@ -147,26 +147,18 @@ namespace MarcerGameDvdLauncher
Console.BackgroundColor = ConsoleColor.DarkGray;
Console.ForegroundColor = ConsoleColor.White;
var topBorder = "+" + new string('-', Math.Max(0, boxWidth - 2)) + "+";
Console.SetCursorPosition(0, topRow);
Console.Write(topBorder);
for (var i = 0; i < boxHeight - 2; i++)
// Fill the box area without borders for cleaner appearance
for (var i = 0; i < boxHeight; i++)
{
var row = topRow + 1 + i;
var content = i < helpLines.Length
? PadToWidth(helpLines[i], boxWidth - 2)
: new string(' ', Math.Max(0, boxWidth - 2));
Console.SetCursorPosition(0, row);
Console.Write("|" + content + "|");
}
var bottomRow = topRow + boxHeight - 1;
if (bottomRow < Console.WindowHeight)
{
var bottomBorder = "+" + new string('-', Math.Max(0, boxWidth - 2)) + "+";
Console.SetCursorPosition(0, bottomRow);
Console.Write(bottomBorder);
var row = topRow + i;
if (row < Console.WindowHeight)
{
Console.SetCursorPosition(0, row);
var content = i == 0 || i == boxHeight - 1 || i >= helpLines.Length + 2
? new string(' ', boxWidth)
: PadToWidth(helpLines[i - 1], boxWidth);
Console.Write(content);
}
}
Console.ResetColor();
}
@@ -176,14 +168,13 @@ namespace MarcerGameDvdLauncher
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",
" ↑ / ↓ Move selection up / down",
" PgUp / PgDn Jump one page up / down",
" Enter / Open folder / launch ZIP with Hatari",
" * Toggle favorite on selected ZIP",
" ← / Backspace Go up one directory (never exceeds root)",
" ? Show this help",
" ESC / q Exit the program",
" ",
" Navigation is strictly limited to RootDirectory.",
" The overlay shows both root and patch layers combined.",
@@ -120,5 +120,11 @@ namespace MarcerGameDvdLauncher
}
if (waitForKey) Console.ReadKey(intercept: true);
}
// Convenience overload: flate argument list instead of explicit array.
public static void ShowConsoleMessage(params string[] lines)
{
ShowConsoleMessage(lines, ConsoleColor.Gray, clear: true, waitForKey: true);
}
}
}