demo mode

This commit is contained in:
2026-08-10 21:15:32 +02:00
parent ca2e46a8a6
commit 7360687da7
6 changed files with 257 additions and 36 deletions
+3
View File
@@ -19,3 +19,6 @@ Thumbs.db
# User-specific configuration (real config, not example)
launcher.config.json
favorites.txt
# Demo directory (generated by demo.sh)
.demo/
+3 -1
View File
@@ -174,6 +174,7 @@ namespace MarcerGameDvdLauncher
ProgramHelpers.FlushInputBuffer();
break;
case ConsoleKey.Enter:
case ConsoleKey.RightArrow:
var oldRelativePath = _navigationController.CurrentRelativePath;
var isDirectory = _gameEntries.Count > 0 && _gameEntries[_navigationController.SelectedIndex].Kind == EntryKind.Directory;
_navigationController.HandleEnter(_gameEntries);
@@ -199,11 +200,11 @@ namespace MarcerGameDvdLauncher
ProgramHelpers.FlushInputBuffer();
break;
case ConsoleKey.Backspace:
case ConsoleKey.LeftArrow:
_navigationController.GoUpDirectory();
ReloadGameEntries();
_navigationController.UpdateScrollOffset(_gameEntries.Count, currentAvailableLines);
_menuRenderer.DrawMenu(_gameEntries, _navigationController.ScrollOffset, _navigationController.SelectedIndex, currentAvailableLines, isFav);
// flush input to avoid leftover key events after directory change
ProgramHelpers.FlushInputBuffer();
break;
case ConsoleKey.PageDown:
@@ -237,6 +238,7 @@ namespace MarcerGameDvdLauncher
ProgramHelpers.FlushInputBuffer();
break;
case ConsoleKey.Escape:
case ConsoleKey.Q:
exitRequested = true;
break;
}
+36 -18
View File
@@ -156,18 +156,8 @@ namespace MarcerGameDvdLauncher
private string BuildLineText(GameEntry e, int width, bool isFavorite)
{
if (width <= 0) return string.Empty;
// Reserve 6 characters for the label area. For directories we show "[DIR] ",
// for ZIPs we use the same width and optionally show a leading '*' when favorited.
string label;
if (e.Kind == EntryKind.Directory)
{
label = "[DIR] ";
}
else
{
// For ZIPs, show '*' after 4 spaces when favorited (keeps 6-char label area).
label = isFavorite ? " * " : new string(' ', 6);
}
string label = GetLabel(e, isFavorite);
// If the console width is smaller than the label, truncate the label
if (width <= label.Length)
@@ -197,21 +187,49 @@ namespace MarcerGameDvdLauncher
return result;
}
// Returns the left label for an entry based on kind, layer status and favorite state.
// Format: [LayerLabel][TypeIndicator] where
// LayerLabel = [BOTH] / [ROOT] / [PTCH] (7 chars)
// TypeIndicator = [DIR] for dirs, " * " or " " for ZIPs (6 chars)
private static string GetLabel(GameEntry e, bool isFavorite)
{
// Layer label (7 chars)
string layer;
if (e.InRoot && e.InPatch) layer = "[BOTH] ";
else if (e.InPatch) layer = "[PTCH] ";
else if (e.InRoot) layer = "[ROOT] ";
else layer = " ";
// Type indicator (6 chars)
string type;
if (e.Kind == EntryKind.Directory)
{
type = "[DIR] ";
}
else
{
type = isFavorite ? " * " : " ";
}
return layer + type; // 13 chars total
}
private ConsoleColor GetColorForEntry(GameEntry e)
{
// Virtual entries (like the Favorites pseudo-folder) should be white
if (e.IsVirtual) return ConsoleColor.White;
if (e.Kind == EntryKind.Directory)
{
if (e.InRoot && e.InPatch) return ConsoleColor.Yellow; // Both layers
if (e.InPatch && !e.InRoot) return ConsoleColor.DarkYellow; // Only patch
if (e.InRoot && !e.InPatch) return ConsoleColor.Gray; // Only root
if (e.InRoot && e.InPatch) return ConsoleColor.Yellow; // Both layers
if (e.InPatch && !e.InRoot) return ConsoleColor.DarkYellow; // Only patch
if (e.InRoot && !e.InPatch) return ConsoleColor.Gray; // Only root
}
else if (e.Kind == EntryKind.Zip)
{
if (e.InRoot && e.InPatch) return ConsoleColor.Green;
if (e.InRoot && !e.InPatch) return ConsoleColor.DarkGreen;
if (e.InPatch && !e.InRoot) return ConsoleColor.Magenta;
if (e.InRoot && e.InPatch) return ConsoleColor.Green; // Both layers
if (e.InRoot && !e.InPatch) return ConsoleColor.DarkGreen; // Only root
if (e.InPatch && !e.InRoot) return ConsoleColor.Magenta; // Only patch
}
return ConsoleColor.DarkGray;
}
+13 -10
View File
@@ -4,16 +4,16 @@ A performant, consistent console launcher for the Hatari emulator on Windows. Co
## Features
- **Overlay/Patch Union:** Recursively merges main and patch directory at every level. Each object/name is shown only once (patch takes precedence).
- **Dynamic, practical color scheme:**
- **Dynamic, practical color scheme with layer labels:**
| Entry | ConsoleColor | Meaning
|------------------------|--------------|---------------------------------------------|
| Folder in both | ConsoleColor.Yellow | Directory in both layers (patch/main)
| Patch-only folder | ConsoleColor.DarkYellow | Directory only in patch layer
| Main-only folder | ConsoleColor.Gray | Directory only in main layer
| ZIP in both | ConsoleColor.Green | ZIP archive in both layers
| Main-only ZIP | ConsoleColor.DarkGreen | ZIP archive only in main layer
| Patch-only ZIP | ConsoleColor.Magenta | ZIP archive only in patch layer
| Entry | Label | ConsoleColor | Meaning
|------------------------|-----------|--------------|---------------------------------------------|
| Folder in both | `[BOTH]` | Yellow | Directory in both layers (patch/main)
| Patch-only folder | `[PTCH]` | DarkYellow | Directory only in patch layer
| Main-only folder | `[ROOT]` | Gray | Directory only in main layer
| ZIP in both | `[BOTH]` | Green | ZIP archive in both layers
| Main-only ZIP | `[ROOT]` | DarkGreen | ZIP archive only in main layer
| Patch-only ZIP | `[PTCH]` | Magenta | ZIP archive only in patch layer
## Roadmap
@@ -57,10 +57,13 @@ A performant, consistent console launcher for the Hatari emulator on Windows. Co
- **Consistent navigation & controls:**
- Arrow up/down: move selection (always visible)
- Arrow right: open folder / launch ZIP with Hatari (same as Enter)
- Arrow left: exactly one level up (same as Backspace)
- Enter: open folder / launch ZIP with Hatari (patch variant always preferred if present)
- Backspace: exactly one level up (never exceeds root)
- ESC: exit the program immediately
- ESC or Q: exit the program immediately
- PageUp/PageDown: jump exactly one screen full (window height - 1)
- `*`: toggle favorite on selected ZIP
- 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
+13 -7
View File
@@ -106,13 +106,19 @@ Note on PatchDirectory semantics:
Implementation note (input flushing):
- To avoid undesired key-repeat / input "afterglow" when the user holds navigation keys, the application performs a best-effort flush of the console input buffer after navigation events. This is implemented by ProgramHelpers.FlushInputBuffer(), which uses the Win32 FlushConsoleInputBuffer API on Windows. This behaviour is intentional and required to provide a responsive navigation experience.
### Color Scheme
- Folder in both layers: **ConsoleColor.Yellow**
- Folder only in patch layer: **ConsoleColor.DarkYellow**
- Folder only in main layer: **ConsoleColor.Gray**
- ZIP in both layers: **ConsoleColor.Green**
- ZIP only in main layer: **ConsoleColor.DarkGreen**
- ZIP only in patch layer: **ConsoleColor.Magenta**
### Color Scheme and Layer Labels
Each entry is displayed with a left label indicating its layer status:
- **`[BOTH]`**: Entry exists in both main and patch layer
- **`[ROOT]`**: Entry exists only in main (root) layer
- **`[PTCH]`**: Entry exists only in patch layer
Color mapping:
- Folder in both layers: **ConsoleColor.Yellow** (`[BOTH]`)
- Folder only in patch layer: **ConsoleColor.DarkYellow** (`[PTCH]`)
- Folder only in main layer: **ConsoleColor.Gray** (`[ROOT]`)
- ZIP in both layers: **ConsoleColor.Green** (`[BOTH]`)
- ZIP only in main layer: **ConsoleColor.DarkGreen** (`[ROOT]`)
- ZIP only in patch layer: **ConsoleColor.Magenta** (`[PTCH]`)
Note: The ConsoleColor mapping above is authoritative for the application. If you change color values in code (MenuRenderer/GetColorForEntry), update this section to keep documentation and implementation in sync.
Executable
+189
View File
@@ -0,0 +1,189 @@
#!/bin/bash
# === Demo setup and run script for MarcerGameDvdLauncher ===
# Creates a structured test directory with main and patch layers,
# populates them with fake ZIPs, then launches the launcher.
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DEMO_DIR="$SCRIPT_DIR/.demo"
ROOT_DIR="$DEMO_DIR/root"
PATCH_DIR="$DEMO_DIR/patch"
CONFIG_FILE="$SCRIPT_DIR/launcher.config.json"
# --- Step 1: Build ---
echo "=== Building MarcerGameDvdLauncher ==="
cd "$SCRIPT_DIR"
dotnet build -c Release --verbosity quiet
echo "[OK] Build successful."
# --- Step 2: Create demo directory structure ---
echo ""
echo "=== Setting up demo directories ==="
# Clean previous demo if it exists
rm -rf "$DEMO_DIR"
mkdir -p "$ROOT_DIR" "$PATCH_DIR"
# Helper: create a fake ZIP (just an empty file with .zip extension)
fake_zip() {
touch "$1"
}
# --- ROOT layer (main DVD content) ---
echo "Creating root layer..."
# === Folder A: TEST-ME — Atari Classics (shared with patch — rich mix + subdirs) ===
mkdir -p "$ROOT_DIR/A/TEST-ME Atari Classics"
fake_zip "$ROOT_DIR/A/TEST-ME Atari Classics/Pac-Man.zip"
fake_zip "$ROOT_DIR/A/TEST-ME Atari Classics/Donkey Kong.zip"
fake_zip "$ROOT_DIR/A/TEST-ME Atari Classics/Galaga.zip"
fake_zip "$ROOT_DIR/A/TEST-ME Atari Classics/Space Invaders.zip"
fake_zip "$ROOT_DIR/A/TEST-ME Atari Classics/Frogger.zip"
fake_zip "$ROOT_DIR/A/TEST-ME Atari Classics/Bomberman.zip"
fake_zip "$ROOT_DIR/A/TEST-ME Atari Classics/Tetris.zip"
# Subdirs: Original and Manual
mkdir -p "$ROOT_DIR/A/TEST-ME Atari Classics/Original"
fake_zip "$ROOT_DIR/A/TEST-ME Atari Classics/Original/Pac-Man (Original).zip"
fake_zip "$ROOT_DIR/A/TEST-ME Atari Classics/Original/Donkey Kong (Original).zip"
mkdir -p "$ROOT_DIR/A/TEST-ME Atari Classics/Manual"
touch "$ROOT_DIR/A/TEST-ME Atari Classics/Manual/Pac-Man.pdf" # PDF — should NOT appear
touch "$ROOT_DIR/A/TEST-ME Atari Classics/Manual/Donkey Kong.pdf" # PDF — should NOT appear
# === Folder B: Platformer (root only) ===
mkdir -p "$ROOT_DIR/B/Platformer"
fake_zip "$ROOT_DIR/B/Platformer/Super Mario Bros.zip"
fake_zip "$ROOT_DIR/B/Platformer/Sonic the Hedgehog.zip"
fake_zip "$ROOT_DIR/B/Platformer/Mega Man.zip"
fake_zip "$ROOT_DIR/B/Platformer/Castlevania.zip"
# === Folder C: Puzzle (root only) ===
mkdir -p "$ROOT_DIR/C/Puzzle"
fake_zip "$ROOT_DIR/C/Puzzle/Columns.zip"
fake_zip "$ROOT_DIR/C/Puzzle/Puyo Puyo.zip"
fake_zip "$ROOT_DIR/C/Puzzle/Klax.zip"
# === Folder D: Shoot'em'up (shared with patch — rich mix) ===
mkdir -p "$ROOT_DIR/D/Shoot'em'up"
fake_zip "$ROOT_DIR/D/Shoot'em'up/R-Type.zip"
fake_zip "$ROOT_DIR/D/Shoot'em'up/Gradius.zip"
fake_zip "$ROOT_DIR/D/Shoot'em'up/1942.zip"
fake_zip "$ROOT_DIR/D/Shoot'em'up/Defender.zip"
fake_zip "$ROOT_DIR/D/Shoot'em'up/Centipede.zip"
fake_zip "$ROOT_DIR/D/Shoot'em'up/Galaxian.zip"
# === Folder E: Racing (root only) ===
mkdir -p "$ROOT_DIR/E/Racing"
fake_zip "$ROOT_DIR/E/Racing/Pole Position.zip"
fake_zip "$ROOT_DIR/E/Racing/Out Run.zip"
fake_zip "$ROOT_DIR/E/Racing/Daytona USA.zip"
# --- PATCH layer (overlay/additions) ---
echo "Creating patch layer..."
# === Folder A: TEST-ME — Atari Classics — overrides + new games ===
mkdir -p "$PATCH_DIR/A/TEST-ME Atari Classics"
fake_zip "$PATCH_DIR/A/TEST-ME Atari Classics/Pac-Man.zip" # [BOTH] override
fake_zip "$PATCH_DIR/A/TEST-ME Atari Classics/Donkey Kong.zip" # [BOTH] override
fake_zip "$PATCH_DIR/A/TEST-ME Atari Classics/Pac-Man Championship.zip" # [PTCH] new
fake_zip "$PATCH_DIR/A/TEST-ME Atari Classics/Donkey Kong Jr.zip" # [PTCH] new
# Subdir Original in patch — adds one more
mkdir -p "$PATCH_DIR/A/TEST-ME Atari Classics/Original"
fake_zip "$PATCH_DIR/A/TEST-ME Atari Classics/Original/Galaga (Original).zip" # [PTCH] in subdir
# === Folder D: Shoot'em'up — overrides + new games ===
mkdir -p "$PATCH_DIR/D/Shoot'em'up"
fake_zip "$PATCH_DIR/D/Shoot'em'up/R-Type.zip" # [BOTH] override
fake_zip "$PATCH_DIR/D/Shoot'em'up/R-Type II.zip" # [PTCH] new
fake_zip "$PATCH_DIR/D/Shoot'em'up/Salamander.zip" # [PTCH] new
# === Folder F: Patch-only folder (not in root) ===
mkdir -p "$PATCH_DIR/F/Hack & Translation"
fake_zip "$PATCH_DIR/F/Hack & Translation/Pac-Man MSX.zip"
fake_zip "$PATCH_DIR/F/Hack & Translation/Donkey Kong Remix.zip"
fake_zip "$PATCH_DIR/F/Hack & Translation/Galaga Special.zip"
echo "[OK] Demo structure created."
echo ""
echo " ROOT (DVD) PATCH (Overlay)"
echo " ────────── ───────────────"
echo " A/TEST-ME Atari Classics/ A/TEST-ME Atari Classics/"
echo " ├── Pac-Man.zip [BOTH] ├── Pac-Man.zip"
echo " ├── Donkey Kong.zip [BOTH] ├── Donkey Kong.zip"
echo " ├── Galaga.zip [ROOT] ├── Pac-Man Championship.zip [PTCH]"
echo " ├── Space Invaders.zip [ROOT] ├── Donkey Kong Jr.zip [PTCH]"
echo " ├── Frogger.zip [ROOT] │"
echo " ├── Bomberman.zip [ROOT] └── Original/"
echo " ├── Tetris.zip [ROOT] └── Galaga (Original).zip [PTCH]"
echo " ├── Original/"
echo " │ ├── Pac-Man (Original).zip [ROOT]"
echo " │ └── Donkey Kong (Original).zip [ROOT]"
echo " └── Manual/ (PDFs — should NOT appear)"
echo " ├── Pac-Man.pdf"
echo " └── Donkey Kong.pdf"
echo " B/Platformer/ (no patch)"
echo " ├── Super Mario Bros.zip [ROOT]"
echo " ├── Sonic.zip [ROOT]"
echo " ├── Mega Man.zip [ROOT]"
echo " └── Castlevania.zip [ROOT]"
echo " C/Puzzle/ (no patch)"
echo " ├── Columns.zip [ROOT]"
echo " ├── Puyo Puyo.zip [ROOT]"
echo " └── Klax.zip [ROOT]"
echo " D/Shoot'em'up/ D/Shoot'em'up/"
echo " ├── R-Type.zip [BOTH] ├── R-Type.zip"
echo " ├── Gradius.zip [ROOT] ├── R-Type II.zip [PTCH]"
echo " ├── 1942.zip [ROOT] └── Salamander.zip [PTCH]"
echo " ├── Defender.zip [ROOT]"
echo " ├── Centipede.zip [ROOT]"
echo " └── Galaxian.zip [ROOT]"
echo " E/Racing/ (no patch)"
echo " ├── Pole Position.zip [ROOT]"
echo " ├── Out Run.zip [ROOT]"
echo " └── Daytona USA.zip [ROOT]"
echo " F/Hack & Translation/ [PTCH]"
echo " ├── Pac-Man MSX.zip"
echo " ├── DK Remix.zip"
echo " └── Galaga Special.zip"
echo ""
# --- Step 3: Create launcher.config.json ---
echo "=== Writing launcher.config.json ==="
# Create a fake Hatari executable for demo (Linux: shell script with .exe extension)
HATARI_FAKE="$DEMO_DIR/hatari.exe"
cat > "$HATARI_FAKE" <<'HATEXEC'
#!/bin/bash
echo "[DEMO] Hatari would launch with: $@"
HATEXEC
chmod +x "$HATARI_FAKE"
cat > "$CONFIG_FILE" <<EOF
{
"RootDirectory": "$ROOT_DIR",
"PatchDirectory": "$PATCH_DIR",
"Hatari": {
"Executable": "$HATARI_FAKE",
"ConfigFile": "",
"ArgsTemplate": "{zip}"
}
}
EOF
echo "[OK] Config written to $CONFIG_FILE"
# Copy config to EXE output directory (app looks for it there)
EXE_DIR="$SCRIPT_DIR/MarcerGameDvdLauncher/bin/Release/net10.0"
cp "$CONFIG_FILE" "$EXE_DIR/launcher.config.json"
echo "[OK] Config copied to $EXE_DIR"
# --- Step 4: Launch the application ---
echo ""
echo "=== Launching MarcerGameDvdLauncher ==="
echo "Controls: Arrow keys, Enter, Backspace, ESC to exit"
echo ""
cd "$SCRIPT_DIR"
dotnet run --project MarcerGameDvdLauncher -c Release