Refactor menu structure and add Flutter-specific input and persistence layers

- Moved menu-related classes to a new structure under `src/menu/`.
- Introduced `WolfMenuPresentation` to handle menu art and mappings.
- Added `MenuManager` tests to ensure menu state reflects game status.
- Implemented `FlutterRendererSettingsPersistence` and `FlutterSaveGamePersistence` for managing settings and save files on desktop platforms.
- Created `Wolf3dFlutterInput` to handle keyboard and mouse input in a Flutter environment.
- Updated README to reflect new package structure and usage instructions.

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-24 18:45:34 +01:00
parent 9f3651b122
commit 5c309c2240
37 changed files with 2356 additions and 1565 deletions
+28 -30
View File
@@ -8,6 +8,8 @@ import 'game_data_picker_manager.dart';
/// GUI-host fallback screen shown when no Wolf3D game data is discovered.
class NoGameDataScreen extends StatelessWidget {
static const WolfMenuPresentation _menu = WolfMenuPresentation.classic();
/// Creates the no-data screen with app-owned setup actions.
const NoGameDataScreen({
super.key,
@@ -67,22 +69,22 @@ class NoGameDataScreen extends StatelessWidget {
static Color _stateColor(GameDataVersionState state) {
switch (state) {
case GameDataVersionState.ready:
return Color(WolfMenuPalette.emphasisColor);
return Color(_menu.emphasisColor);
case GameDataVersionState.checksumWarning:
return Color(WolfMenuPalette.warningColor);
return Color(_menu.warningColor);
case GameDataVersionState.incomplete:
return Color(WolfMenuPalette.mutedColor);
return Color(_menu.mutedColor);
}
}
static Color _fileStateColor(GameDataFileState state) {
switch (state) {
case GameDataFileState.ready:
return Color(WolfMenuPalette.emphasisColor);
return Color(_menu.emphasisColor);
case GameDataFileState.warning:
return Color(WolfMenuPalette.warningColor);
return Color(_menu.warningColor);
case GameDataFileState.missing:
return Color(WolfMenuPalette.mutedColor);
return Color(_menu.mutedColor);
}
}
@@ -92,7 +94,7 @@ class NoGameDataScreen extends StatelessWidget {
scanResult?.readyVersions ?? <GameDataVersionAnalysis>[];
return Scaffold(
backgroundColor: Color(WolfMenuPalette.backgroundColor),
backgroundColor: Color(_menu.backgroundColor),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints viewportConstraints) {
return SingleChildScrollView(
@@ -106,9 +108,9 @@ class NoGameDataScreen extends StatelessWidget {
constraints: const BoxConstraints(maxWidth: 640),
child: DecoratedBox(
decoration: BoxDecoration(
color: Color(WolfMenuPalette.panelColor),
color: Color(_menu.panelColor),
border: Border.all(
color: Color(WolfMenuPalette.borderColor),
color: Color(_menu.borderColor),
width: 2,
),
),
@@ -121,7 +123,7 @@ class NoGameDataScreen extends StatelessWidget {
Text(
'WOLF3D DATA NOT FOUND',
style: TextStyle(
color: Color(WolfMenuPalette.titleColor),
color: Color(_menu.titleColor),
fontSize: 24,
fontWeight: FontWeight.bold,
),
@@ -131,7 +133,7 @@ class NoGameDataScreen extends StatelessWidget {
'No game files were discovered.\n\n'
'Select a game-data directory, or select one or more game-data files.',
style: TextStyle(
color: Color(WolfMenuPalette.bodyColor),
color: Color(_menu.bodyColor),
fontSize: 15,
height: 1.4,
),
@@ -140,7 +142,7 @@ class NoGameDataScreen extends StatelessWidget {
Text(
'A complete version can be loaded directly or imported into the app config folder.',
style: TextStyle(
color: Color(WolfMenuPalette.emphasisColor),
color: Color(_menu.emphasisColor),
fontSize: 14,
height: 1.35,
fontWeight: FontWeight.w600,
@@ -179,7 +181,7 @@ class NoGameDataScreen extends StatelessWidget {
child: Text(
'Scanning selected locations...',
style: TextStyle(
color: Color(WolfMenuPalette.bodyColor),
color: Color(_menu.bodyColor),
fontSize: 13,
height: 1.3,
),
@@ -188,8 +190,8 @@ class NoGameDataScreen extends StatelessWidget {
if (scanResult != null) ...[
const SizedBox(height: 18),
_ScanSummary(
bodyColor: Color(WolfMenuPalette.bodyColor),
mutedColor: Color(WolfMenuPalette.mutedColor),
bodyColor: Color(_menu.bodyColor),
mutedColor: Color(_menu.mutedColor),
scanResult: scanResult!,
),
const SizedBox(height: 12),
@@ -197,15 +199,11 @@ class NoGameDataScreen extends StatelessWidget {
(GameDataVersionAnalysis analysis) => Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _VersionCard(
panelColor: Color(
WolfMenuPalette.backgroundColor,
),
borderColor: Color(
WolfMenuPalette.borderColor,
),
titleColor: Color(WolfMenuPalette.titleColor),
bodyColor: Color(WolfMenuPalette.bodyColor),
mutedColor: Color(WolfMenuPalette.mutedColor),
panelColor: Color(_menu.backgroundColor),
borderColor: Color(_menu.borderColor),
titleColor: Color(_menu.titleColor),
bodyColor: Color(_menu.bodyColor),
mutedColor: Color(_menu.mutedColor),
analysis: analysis,
),
),
@@ -217,18 +215,18 @@ class NoGameDataScreen extends StatelessWidget {
initialValue:
selectedReadyVersion ??
readyVersions.first.version,
dropdownColor: Color(WolfMenuPalette.panelColor),
dropdownColor: Color(_menu.panelColor),
style: TextStyle(
color: Color(WolfMenuPalette.bodyColor),
color: Color(_menu.bodyColor),
),
decoration: InputDecoration(
labelText: 'Complete version',
labelStyle: TextStyle(
color: Color(WolfMenuPalette.bodyColor),
color: Color(_menu.bodyColor),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Color(WolfMenuPalette.borderColor),
color: Color(_menu.borderColor),
),
),
),
@@ -275,7 +273,7 @@ class NoGameDataScreen extends StatelessWidget {
child: Text(
pickerError!.trim(),
style: TextStyle(
color: Color(WolfMenuPalette.emphasisColor),
color: Color(_menu.emphasisColor),
fontSize: 13,
height: 1.3,
fontWeight: FontWeight.w600,
@@ -289,7 +287,7 @@ class NoGameDataScreen extends StatelessWidget {
child: Text(
'Configured data directory: ${configuredDataDirectory!.trim()}',
style: TextStyle(
color: Color(WolfMenuPalette.bodyColor),
color: Color(_menu.bodyColor),
fontSize: 13,
height: 1.3,
),