/// Active gameplay screen for the Flutter host. library; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:wolf_3d_dart/wolf_3d_engine.dart'; import 'package:wolf_3d_dart/wolf_3d_renderer.dart'; import 'package:wolf_3d_flutter/renderer/wolf_3d_ascii_renderer.dart'; import 'package:wolf_3d_flutter/renderer/wolf_3d_flutter_renderer.dart'; import 'package:wolf_3d_flutter/renderer/wolf_3d_glsl_renderer.dart'; import 'package:wolf_3d_flutter/wolf_3d_flutter.dart'; /// Launches a [WolfEngine] via [Wolf3d] and exposes renderer/input integrations. class GameScreen extends StatefulWidget { /// Shared application facade owning the engine, audio, and input. final Wolf3d wolf3d; /// Optional host-level shortcut override. /// /// Return `true` when the event was consumed. Handlers may call /// [Wolf3dFlutterInput.suppressActionOnce] to keep actions from reaching the /// engine update loop. final HostShortcutHandler? hostShortcutHandler; /// Declarative host shortcut registry used when [hostShortcutHandler] is null. final HostShortcutRegistry hostShortcutRegistry; /// Optional host app-exit hook. /// /// Defaults to [SystemNavigator.pop] in production, but tests can inject a /// fake callback to assert quit behavior. final Future Function()? appExitHandler; /// Skips engine bootstrap for lightweight widget tests. @visibleForTesting final bool skipEngineBootstrapForTest; /// Creates a gameplay screen driven by [wolf3d]. const GameScreen({ required this.wolf3d, this.hostShortcutHandler, this.hostShortcutRegistry = HostShortcutRegistry.defaults, this.appExitHandler, this.skipEngineBootstrapForTest = false, super.key, }); @override State createState() => _GameScreenState(); } class _GameScreenState extends State { WolfEngine? _engine; late final GameAppLifecycleManager _appLifecycleManager; late final GameDisplayManager _displayManager; late final GameScreenInputManager _inputManager; late final GamePersistenceManager _persistenceManager; /// Mirrors [WolfRendererSettings.mode] into the Flutter renderer enum. GameRendererMode _rendererMode = GameRendererMode.hardware; @override void initState() { super.initState(); _appLifecycleManager = GameAppLifecycleManager( shutdownAudio: widget.wolf3d.shutdownAudio, appExitHandler: widget.appExitHandler, ); _displayManager = GameDisplayManager(); _persistenceManager = GamePersistenceManager(); _inputManager = GameScreenInputManager( input: widget.wolf3d.input, hostShortcutHandler: widget.hostShortcutHandler, hostShortcutRegistry: widget.hostShortcutRegistry, onToggleFullscreen: _displayManager.toggleFullscreen, ); if (widget.skipEngineBootstrapForTest) { return; } const Set supportedModes = { WolfRendererMode.hardware, WolfRendererMode.software, WolfRendererMode.ascii, }; final engine = widget.wolf3d.launchEngine( rendererCapabilities: const WolfRendererCapabilities( supportedModes: supportedModes, supportsAsciiThemes: true, supportsHardwareEffects: true, supportsBloom: true, supportsFpsCounter: true, ), rendererSettings: const WolfRendererSettings( mode: WolfRendererMode.hardware, ), onRendererSettingsChanged: (settings) { unawaited(_persistenceManager.saveRendererSettings(settings)); if (mounted) { setState(() { _rendererMode = gameRendererModeFromSettings(settings); }); } }, onGameWon: () { _engine?.difficulty = null; widget.wolf3d.clearActiveDifficulty(); Navigator.of(context).pop(); }, onQuit: () { unawaited(_appLifecycleManager.quitApplication()); }, saveGamePersistence: _persistenceManager.saveGamePersistence, ); _engine = engine; _rendererMode = gameRendererModeFromSettings(engine.rendererSettings); unawaited(_persistenceManager.restoreRendererSettings(engine)); } @override void dispose() { unawaited(widget.wolf3d.shutdownAudio()); super.dispose(); } @override Widget build(BuildContext context) { final engine = _engine; if (engine == null) { return const Scaffold(body: SizedBox.shrink()); } final WolfRendererSettings settings = engine.rendererSettings; final Widget renderer = switch (_rendererMode) { GameRendererMode.software => WolfFlutterRenderer( engine: engine, onKeyEvent: _handleRendererKeyEvent, ), GameRendererMode.ascii => WolfAsciiRenderer( engine: engine, theme: settings.asciiThemeId == WolfRendererSettings.asciiThemeQuadrant ? AsciiThemes.quadrant : AsciiThemes.blocks, onKeyEvent: _handleRendererKeyEvent, ), GameRendererMode.hardware => WolfGlslRenderer( engine: engine, effectsEnabled: settings.hardwareEffectsEnabled, bloomEnabled: settings.bloomEnabled, onKeyEvent: _handleRendererKeyEvent, onUnavailable: _handleGlslUnavailable, ), }; return PopScope( canPop: engine.difficulty != null, onPopInvokedWithResult: (didPop, _) { if (!didPop && engine.difficulty == null) { widget.wolf3d.input.queueBackAction(); } }, child: Scaffold( floatingActionButton: widget.wolf3d.isDebugEnabled && engine.difficulty != null ? FloatingActionButton( onPressed: _openDebugTools, tooltip: 'Open Debug Tools', child: const Icon(Icons.bug_report), ) : null, body: LayoutBuilder( builder: (context, constraints) { return Listener( onPointerDown: (event) { widget.wolf3d.input.onPointerDown(event); }, onPointerUp: widget.wolf3d.input.onPointerUp, onPointerMove: widget.wolf3d.input.onPointerMove, onPointerHover: widget.wolf3d.input.onPointerMove, child: Stack( children: [ renderer, if (!engine.isInitialized) Container( color: Colors.black, child: const Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(color: Colors.teal), SizedBox(height: 20), Text( 'GET PSYCHED!', style: TextStyle( color: Colors.teal, fontFamily: 'monospace', ), ), ], ), ), ), // A second full-screen overlay keeps the presentation simple while // the engine is still warming up or decoding the first frame. if (!engine.isInitialized) Container( color: Colors.black, child: const Center( child: CircularProgressIndicator(color: Colors.teal), ), ), ], ), ); }, ), ), ); } void _handleRendererKeyEvent(KeyEvent event) { _inputManager.handleRendererKeyEventForHost( event: event, engine: _engine, rendererMode: _rendererMode, onFpsToggled: (WolfEngine activeEngine) { setState(() => activeEngine.toggleFpsCounter()); }, ); } void _handleGlslUnavailable() { handleGlslUnavailable( isMounted: mounted, rendererMode: _rendererMode, engine: _engine, ); } void _openDebugTools() { Navigator.of(context).push( MaterialPageRoute( builder: (_) => DebugToolsScreen(wolf3d: widget.wolf3d), ), ); } }