feat: Implement Change View and Renderer Options menus
- Added functionality to display and navigate the Change View menu in SixelRenderer and SoftwareRenderer. - Introduced methods to draw the Change View and Renderer Options menus, including handling cursor and selection states. - Updated WolfClassicMenuArt to include a customize label for the new menu. - Enhanced WolfMenuScreen to support new menu states. - Created tests for Change View menu interactions, ensuring proper transitions and renderer settings toggling. - Implemented persistence for renderer settings in Flutter, allowing settings to be saved and loaded from a local file. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import 'package:window_manager/window_manager.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_input.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_renderer.dart';
|
||||
import 'package:wolf_3d_flutter/renderer_settings_persistence_flutter.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d_input_flutter.dart';
|
||||
import 'package:wolf_3d_gui/screens/debug_tools_screen.dart';
|
||||
@@ -139,20 +140,38 @@ class GameScreen extends StatefulWidget {
|
||||
|
||||
class _GameScreenState extends State<GameScreen> {
|
||||
late final WolfEngine _engine;
|
||||
final FlutterRendererSettingsPersistence _persistence =
|
||||
FlutterRendererSettingsPersistence();
|
||||
|
||||
/// Current active renderer implementation.
|
||||
/// Mirrors [WolfRendererSettings.mode] into the Flutter renderer enum.
|
||||
RendererMode _rendererMode = RendererMode.hardware;
|
||||
|
||||
/// Active ASCII glyph theme used by [RendererMode.ascii].
|
||||
AsciiTheme _asciiTheme = AsciiThemes.blocks;
|
||||
|
||||
/// Whether CRT post-processing is enabled in [RendererMode.hardware].
|
||||
bool _glslEffectsEnabled = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
const Set<WolfRendererMode> supportedModes = <WolfRendererMode>{
|
||||
WolfRendererMode.hardware,
|
||||
WolfRendererMode.software,
|
||||
WolfRendererMode.ascii,
|
||||
};
|
||||
_engine = widget.wolf3d.launchEngine(
|
||||
rendererCapabilities: const WolfRendererCapabilities(
|
||||
supportedModes: supportedModes,
|
||||
supportsAsciiThemes: true,
|
||||
supportsHardwareEffects: true,
|
||||
supportsFpsCounter: true,
|
||||
),
|
||||
rendererSettings: const WolfRendererSettings(
|
||||
mode: WolfRendererMode.hardware,
|
||||
),
|
||||
onRendererSettingsChanged: (settings) {
|
||||
unawaited(_persistence.save(settings));
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_syncRendererModeFrom(settings);
|
||||
});
|
||||
}
|
||||
},
|
||||
onGameWon: () {
|
||||
_engine.difficulty = null;
|
||||
widget.wolf3d.clearActiveDifficulty();
|
||||
@@ -162,6 +181,30 @@ class _GameScreenState extends State<GameScreen> {
|
||||
SystemNavigator.pop();
|
||||
},
|
||||
);
|
||||
_syncRendererModeFrom(_engine.rendererSettings);
|
||||
_loadPersistedSettings();
|
||||
}
|
||||
|
||||
Future<void> _loadPersistedSettings() async {
|
||||
final WolfRendererSettings? saved = await _persistence.load();
|
||||
if (saved != null && mounted) {
|
||||
_engine.updateRendererSettings(saved);
|
||||
}
|
||||
}
|
||||
|
||||
void _syncRendererModeFrom(WolfRendererSettings settings) {
|
||||
switch (settings.mode) {
|
||||
case WolfRendererMode.hardware:
|
||||
_rendererMode = RendererMode.hardware;
|
||||
break;
|
||||
case WolfRendererMode.software:
|
||||
_rendererMode = RendererMode.software;
|
||||
break;
|
||||
case WolfRendererMode.ascii:
|
||||
case WolfRendererMode.sixel:
|
||||
_rendererMode = RendererMode.ascii;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -222,17 +265,6 @@ class _GameScreenState extends State<GameScreen> {
|
||||
child: CircularProgressIndicator(color: Colors.teal),
|
||||
),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
top: 16,
|
||||
right: 16,
|
||||
child: Text(
|
||||
'<${widget.wolf3d.input.rendererToggleKeyLabel}> ${_rendererMode.name}${_activeModeOverlayHint()} <${widget.wolf3d.input.fpsToggleKeyLabel}> FPS ${_engine.showFpsCounter ? 'On' : 'Off'}',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -245,6 +277,7 @@ class _GameScreenState extends State<GameScreen> {
|
||||
Widget _buildRenderer() {
|
||||
// Keep all renderers behind the same engine so mode switching does not
|
||||
// reset level state or audio playback.
|
||||
final WolfRendererSettings settings = _engine.rendererSettings;
|
||||
switch (_rendererMode) {
|
||||
case RendererMode.software:
|
||||
return WolfFlutterRenderer(
|
||||
@@ -252,15 +285,19 @@ class _GameScreenState extends State<GameScreen> {
|
||||
onKeyEvent: _handleRendererKeyEvent,
|
||||
);
|
||||
case RendererMode.ascii:
|
||||
final AsciiTheme theme =
|
||||
settings.asciiThemeId == WolfRendererSettings.asciiThemeQuadrant
|
||||
? AsciiThemes.quadrant
|
||||
: AsciiThemes.blocks;
|
||||
return WolfAsciiRenderer(
|
||||
engine: _engine,
|
||||
theme: _asciiTheme,
|
||||
theme: theme,
|
||||
onKeyEvent: _handleRendererKeyEvent,
|
||||
);
|
||||
case RendererMode.hardware:
|
||||
return WolfGlslRenderer(
|
||||
engine: _engine,
|
||||
effectsEnabled: _glslEffectsEnabled,
|
||||
effectsEnabled: settings.hardwareEffectsEnabled,
|
||||
onKeyEvent: _handleRendererKeyEvent,
|
||||
onUnavailable: _onGlslUnavailable,
|
||||
);
|
||||
@@ -279,67 +316,31 @@ class _GameScreenState extends State<GameScreen> {
|
||||
}
|
||||
|
||||
if (event.logicalKey == widget.wolf3d.input.rendererToggleKey) {
|
||||
setState(_cycleRendererMode);
|
||||
_engine.cycleRendererMode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.logicalKey == widget.wolf3d.input.fpsToggleKey) {
|
||||
setState(_toggleFpsCounter);
|
||||
setState(() => _engine.toggleFpsCounter());
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.logicalKey == widget.wolf3d.input.asciiThemeCycleKey) {
|
||||
if (_rendererMode == RendererMode.ascii) {
|
||||
setState(_cycleAsciiTheme);
|
||||
_engine.cycleAsciiTheme();
|
||||
} else if (_rendererMode == RendererMode.hardware) {
|
||||
setState(_toggleGlslEffects);
|
||||
_engine.toggleHardwareEffects();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _activeModeOverlayHint() {
|
||||
if (_rendererMode == RendererMode.ascii) {
|
||||
return ' <${widget.wolf3d.input.asciiThemeCycleKeyLabel}> ${_asciiTheme.name}';
|
||||
}
|
||||
if (_rendererMode == RendererMode.hardware) {
|
||||
return ' <${widget.wolf3d.input.asciiThemeCycleKeyLabel}> Effects ${_glslEffectsEnabled ? 'on' : 'off'}';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
void _cycleRendererMode() {
|
||||
switch (_rendererMode) {
|
||||
case RendererMode.hardware:
|
||||
_rendererMode = RendererMode.software;
|
||||
break;
|
||||
case RendererMode.software:
|
||||
_rendererMode = RendererMode.ascii;
|
||||
break;
|
||||
case RendererMode.ascii:
|
||||
_rendererMode = RendererMode.hardware;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _onGlslUnavailable() {
|
||||
if (!mounted || _rendererMode != RendererMode.hardware) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_rendererMode = RendererMode.software;
|
||||
});
|
||||
}
|
||||
|
||||
void _toggleFpsCounter() {
|
||||
_engine.showFpsCounter = !_engine.showFpsCounter;
|
||||
}
|
||||
|
||||
void _cycleAsciiTheme() {
|
||||
_asciiTheme = AsciiThemes.nextOf(_asciiTheme);
|
||||
}
|
||||
|
||||
void _toggleGlslEffects() {
|
||||
_glslEffectsEnabled = !_glslEffectsEnabled;
|
||||
_engine.updateRendererSettings(
|
||||
_engine.rendererSettings.copyWith(mode: WolfRendererMode.software),
|
||||
);
|
||||
}
|
||||
|
||||
void _openDebugTools() {
|
||||
|
||||
Reference in New Issue
Block a user