Moved all widgets and logic from gui app to Flutter package

- Implemented DebugToolsScreen for navigation to asset galleries.
- Created GameScreen to manage gameplay and renderer integrations.
- Added NoGameDataScreen to handle scenarios with missing game data.
- Developed SpriteGallery for visual browsing of sprite assets.
- Introduced VgaGallery for displaying VGA images from game data.
- Added GalleryGameSelector widget for selecting game variants in galleries.
- Created Wolf3dApp as the main application shell for managing game states.
- Implemented WolfMenuShell for consistent menu layouts across screens.
- Enhanced Wolf3d class to support debug mode and related functionalities.
- Updated pubspec.yaml to include window_manager dependency.
- Added tests for game screen lifecycle and debug mode functionalities.

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-23 18:44:32 +01:00
parent cbe2633ceb
commit 5a2681e89b
21 changed files with 963 additions and 590 deletions
@@ -0,0 +1,61 @@
library;
import 'package:flutter/material.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
/// Human-readable title for a game variant in gallery selectors.
String formatGalleryGameTitle(GameVersion version) {
switch (version) {
case GameVersion.shareware:
return 'SHAREWARE';
case GameVersion.retail:
return 'RETAIL';
case GameVersion.spearOfDestiny:
return 'SPEAR OF DESTINY';
case GameVersion.spearOfDestinyDemo:
return 'SOD DEMO';
}
}
/// Selects which discovered game data set gallery screens should display.
class GalleryGameSelector extends StatelessWidget {
final Wolf3d wolf3d;
final WolfensteinData selectedGame;
final ValueChanged<WolfensteinData> onSelected;
const GalleryGameSelector({
super.key,
required this.wolf3d,
required this.selectedGame,
required this.onSelected,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(right: 12),
child: Center(
child: DropdownButtonHideUnderline(
child: DropdownButton<WolfensteinData>(
value: selectedGame,
borderRadius: BorderRadius.circular(8),
dropdownColor: Theme.of(context).colorScheme.surface,
iconEnabledColor: Theme.of(context).colorScheme.onSurface,
onChanged: (WolfensteinData? game) {
if (game != null) {
onSelected(game);
}
},
items: wolf3d.availableGames.map((WolfensteinData game) {
return DropdownMenuItem<WolfensteinData>(
value: game,
child: Text(formatGalleryGameTitle(game.version)),
);
}).toList(),
),
),
),
);
}
}
@@ -0,0 +1,22 @@
library;
import 'package:flutter/material.dart';
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
/// Minimal app shell that binds a prepared [Wolf3d] instance to host screens.
class Wolf3dApp extends StatelessWidget {
/// Shared initialized facade that owns game data, input, and audio services.
final Wolf3d wolf3d;
const Wolf3dApp({
super.key,
required this.wolf3d,
});
@override
Widget build(BuildContext context) {
return wolf3d.availableGames.isEmpty
? const NoGameDataScreen()
: GameScreen(wolf3d: wolf3d);
}
}
@@ -0,0 +1,95 @@
/// Shared shell for Wolf3D-style menu screens.
library;
import 'package:flutter/material.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_flutter/renderer/wolf_3d_asset_painter.dart';
/// Provides a common menu layout with panel framing and optional bottom art.
class WolfMenuShell extends StatelessWidget {
/// Full-screen background color behind the panel.
final Color backgroundColor;
/// Solid panel fill used for the menu content area.
final Color panelColor;
/// Optional heading shown above the panel (text or image).
final Widget? header;
/// Primary menu content rendered inside the panel.
final Widget panelChild;
/// Optional centered VGA image anchored near the bottom of the screen.
final VgaImage? bottomSprite;
/// Width of the menu panel.
final double panelWidth;
/// Padding applied around [panelChild] inside the panel.
final EdgeInsets panelPadding;
/// Scale factor for [bottomSprite].
final double bottomSpriteScale;
/// Distance from the bottom edge for [bottomSprite].
final double bottomOffset;
/// Vertical spacing between [header] and the panel.
final double headerSpacing;
const WolfMenuShell({
super.key,
required this.backgroundColor,
required this.panelColor,
required this.panelChild,
this.header,
this.bottomSprite,
this.panelWidth = 520,
this.panelPadding = const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
this.bottomSpriteScale = 3,
this.bottomOffset = 20,
this.headerSpacing = 14,
});
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(
child: ColoredBox(color: backgroundColor),
),
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (header case final Widget value) value,
if (header != null) SizedBox(height: headerSpacing),
Container(
width: panelWidth,
padding: panelPadding,
color: panelColor,
child: panelChild,
),
],
),
),
if (bottomSprite != null)
Positioned(
left: 0,
right: 0,
bottom: bottomOffset,
child: Center(
child: SizedBox(
width: bottomSprite!.width * bottomSpriteScale,
height: bottomSprite!.height * bottomSpriteScale,
child: WolfAssetPainter.vga(bottomSprite),
),
),
),
],
);
}
}