mirror of
https://github.com/skoelle/marcer-gamedvd-launcher.git
synced 2026-09-17 18:50:25 +00:00
demo mode
This commit is contained in:
@@ -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/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user