Refactor menu rendering and improve projection sampling
- Updated AsciiRasterizer to support game and episode selection menus with improved layout and cursor handling. - Enhanced SixelRasterizer and SoftwareRasterizer to modularize menu drawing logic for game and episode selection. - Introduced new methods for drawing menus and applying fade effects across rasterizers. - Adjusted wall texture sampling in Rasterizer to anchor to projection height center for consistent rendering. - Added tests for wall texture sampling behavior to ensure legacy compatibility and new functionality. - Modified Flutter audio adapter to use nullable access for active game and adjusted game selection logic in the main class. - Cleaned up input handling in Wolf3dFlutterInput by removing unused menu tap variables. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -37,6 +37,14 @@ void main() async {
|
||||
recursive: true,
|
||||
);
|
||||
|
||||
if (availableGames.isEmpty) {
|
||||
stderr.writeln('\nNo Wolf3D game files were found at: $targetPath');
|
||||
stderr.writeln(
|
||||
'Please provide valid game data files before starting the CLI host.',
|
||||
);
|
||||
exitCleanly(1);
|
||||
}
|
||||
|
||||
CliGameLoop? gameLoop;
|
||||
|
||||
void stopAndExit(int code) {
|
||||
@@ -45,7 +53,7 @@ void main() async {
|
||||
}
|
||||
|
||||
final engine = WolfEngine(
|
||||
data: availableGames.values.first,
|
||||
availableGames: availableGames.values.toList(growable: false),
|
||||
startingEpisode: 0,
|
||||
frameBuffer: FrameBuffer(
|
||||
stdout.terminalColumns,
|
||||
|
||||
@@ -6,7 +6,7 @@ library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
|
||||
import 'package:wolf_3d_gui/screens/game_select_screen.dart';
|
||||
import 'package:wolf_3d_gui/screens/game_screen.dart';
|
||||
|
||||
/// Creates the application shell after loading available Wolf3D data sets.
|
||||
void main() async {
|
||||
@@ -16,7 +16,65 @@ void main() async {
|
||||
|
||||
runApp(
|
||||
MaterialApp(
|
||||
home: GameSelectScreen(wolf3d: wolf3d),
|
||||
home: wolf3d.availableGames.isEmpty
|
||||
? const _NoGameDataScreen()
|
||||
: GameScreen(wolf3d: wolf3d),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _NoGameDataScreen extends StatelessWidget {
|
||||
const _NoGameDataScreen();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF140000),
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 640),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF590002),
|
||||
border: Border.all(color: const Color(0xFFB00000), width: 2),
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'WOLF3D DATA NOT FOUND',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFFFF700),
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'No game files were discovered.\n\n'
|
||||
'Add Wolfenstein 3D data files to one of these locations:\n'
|
||||
'- packages/wolf_3d_assets/assets/retail\n'
|
||||
'- packages/wolf_3d_assets/assets/shareware\n'
|
||||
'- or a discoverable local game-data folder.\n\n'
|
||||
'Restart the app after adding the files.',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 15,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,28 +51,9 @@ class _GameScreenState extends State<GameScreen> {
|
||||
child: Scaffold(
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final viewportRect = _menuViewportRect(
|
||||
Size(constraints.maxWidth, constraints.maxHeight),
|
||||
);
|
||||
|
||||
return Listener(
|
||||
onPointerDown: (event) {
|
||||
widget.wolf3d.input.onPointerDown(event);
|
||||
if (_engine.difficulty == null &&
|
||||
viewportRect.width > 0 &&
|
||||
viewportRect.height > 0 &&
|
||||
viewportRect.contains(event.localPosition)) {
|
||||
final normalizedX =
|
||||
(event.localPosition.dx - viewportRect.left) /
|
||||
viewportRect.width;
|
||||
final normalizedY =
|
||||
(event.localPosition.dy - viewportRect.top) /
|
||||
viewportRect.height;
|
||||
widget.wolf3d.input.queueMenuTap(
|
||||
x: normalizedX,
|
||||
y: normalizedY,
|
||||
);
|
||||
}
|
||||
},
|
||||
onPointerUp: widget.wolf3d.input.onPointerUp,
|
||||
onPointerMove: widget.wolf3d.input.onPointerMove,
|
||||
@@ -148,32 +129,4 @@ class _GameScreenState extends State<GameScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Rect _menuViewportRect(Size availableSize) {
|
||||
if (availableSize.width <= 0 || availableSize.height <= 0) {
|
||||
return Rect.zero;
|
||||
}
|
||||
|
||||
const double aspect = 4 / 3;
|
||||
final double outerPadding = _useAsciiMode ? 0.0 : 16.0;
|
||||
final double maxWidth = (availableSize.width - (outerPadding * 2)).clamp(
|
||||
1.0,
|
||||
double.infinity,
|
||||
);
|
||||
final double maxHeight = (availableSize.height - (outerPadding * 2)).clamp(
|
||||
1.0,
|
||||
double.infinity,
|
||||
);
|
||||
|
||||
double viewportWidth = maxWidth;
|
||||
double viewportHeight = viewportWidth / aspect;
|
||||
if (viewportHeight > maxHeight) {
|
||||
viewportHeight = maxHeight;
|
||||
viewportWidth = viewportHeight * aspect;
|
||||
}
|
||||
|
||||
final double left = (availableSize.width - viewportWidth) / 2;
|
||||
final double top = (availableSize.height - viewportHeight) / 2;
|
||||
return Rect.fromLTWH(left, top, viewportWidth, viewportHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/// Game-selection screen shown after the GUI host discovers available assets.
|
||||
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';
|
||||
import 'package:wolf_3d_gui/screens/episode_screen.dart';
|
||||
|
||||
/// Lists every discovered data set and lets the user choose the active one.
|
||||
class GameSelectScreen extends StatelessWidget {
|
||||
/// Shared application facade that owns discovered games, audio, and input.
|
||||
final Wolf3d wolf3d;
|
||||
|
||||
/// Creates the game-selection screen for the supplied [wolf3d] session.
|
||||
const GameSelectScreen({super.key, required this.wolf3d});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: ListView.builder(
|
||||
itemCount: wolf3d.availableGames.length,
|
||||
itemBuilder: (context, i) {
|
||||
final WolfensteinData data = wolf3d.availableGames[i];
|
||||
final GameVersion version = data.version;
|
||||
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Text(version.name),
|
||||
onTap: () {
|
||||
wolf3d.setActiveGame(data);
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => EpisodeScreen(wolf3d: wolf3d),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user