refactor: decompose LauncherApp, centralize magic strings/constants, configurable colors

- Extract InputController from AppHost (key handling + ReloadGameEntries)
- Centralize hardcoded values as code constants (non-configurable):
  FavoritesRootName, DefaultFileName, DefaultTitle, scroll fractions,
  AvailableLines helper, removed redundant ArgsTemplate fallback
- Make color scheme configurable via 'Colors' section in launcher.config.json
  with Enum.TryParse + default fallback; MenuRenderer uses constructor injection
- Clean up MenuRenderer: remove dead maxRow logic, unify cache sizing via EnsureCache
- Document intentional error swallowing in FavoritesService.Save/UIErrorService
- Update README + AGENTS.md with configurable color docs
- Remove redundant ArgsTemplate fallback (config is single source of truth)
This commit is contained in:
2026-08-11 01:33:33 +02:00
parent 7dca82baa8
commit 62968dbcd1
13 changed files with 551 additions and 319 deletions
@@ -5,6 +5,12 @@ namespace MarcerGameDvdLauncher
{
public class NavigationController
{
// Scroll fractions: when the selection cursor reaches 2/3 of the visible window
// height from the top, the list scrolls down; when it reaches 1/3, it scrolls up.
// Not configurable — these ratios are the established navigation behaviour.
private const double BottomScrollFraction = 2.0 / 3.0;
private const double TopScrollFraction = 1.0 / 3.0;
public int SelectedIndex { get; private set; } = 0;
public int ScrollOffset { get; private set; } = 0;
public string CurrentRelativePath { get; private set; } = "";
@@ -99,12 +105,12 @@ namespace MarcerGameDvdLauncher
if (availableLines < 1) availableLines = 1;
if (entryCount <= availableLines) { ScrollOffset = 0; return; }
if (SelectedIndex == 0) { ScrollOffset = 0; return; }
int bottomScrollTrigger = ScrollOffset + (int)(availableLines * 2 / 3.0);
int topScrollTrigger = ScrollOffset + (int)(availableLines * 1 / 3.0);
int bottomScrollTrigger = ScrollOffset + (int)(availableLines * BottomScrollFraction);
int topScrollTrigger = ScrollOffset + (int)(availableLines * TopScrollFraction);
if (SelectedIndex >= bottomScrollTrigger && (ScrollOffset + availableLines) < entryCount)
ScrollOffset = SelectedIndex - (int)(availableLines * 2 / 3.0);
ScrollOffset = SelectedIndex - (int)(availableLines * BottomScrollFraction);
else if (SelectedIndex < topScrollTrigger && ScrollOffset > 0)
ScrollOffset = SelectedIndex - (int)(availableLines * 1 / 3.0);
ScrollOffset = SelectedIndex - (int)(availableLines * TopScrollFraction);
if (ScrollOffset < 0) ScrollOffset = 0;
if (ScrollOffset > entryCount - availableLines)
ScrollOffset = entryCount - availableLines;