LCOV - code coverage report
Current view: top level - lib/src/game/game_widget - game_widget.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 51 89 57.3 %
Date: 2021-08-10 15:50:53 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:flutter/rendering.dart';
       2             : import 'package:flutter/widgets.dart';
       3             : 
       4             : import '../../../extensions.dart';
       5             : import '../../extensions/size.dart';
       6             : import '../game.dart';
       7             : import '../game_render_box.dart';
       8             : import 'gestures.dart';
       9             : 
      10             : typedef GameLoadingWidgetBuilder = Widget Function(
      11             :   BuildContext,
      12             : );
      13             : 
      14             : typedef GameErrorWidgetBuilder = Widget Function(
      15             :   BuildContext,
      16             :   Object error,
      17             : );
      18             : 
      19             : typedef OverlayWidgetBuilder<T extends Game> = Widget Function(
      20             :   BuildContext context,
      21             :   T game,
      22             : );
      23             : 
      24             : /// A [StatefulWidget] that is in charge of attaching a [Game] instance into the flutter tree
      25             : ///
      26             : class GameWidget<T extends Game> extends StatefulWidget {
      27             :   /// The game instance in which this widget will render
      28             :   final T game;
      29             : 
      30             :   /// The text direction to be used in text elements in a game.
      31             :   final TextDirection? textDirection;
      32             : 
      33             :   /// Builder to provide a widget tree to be built whilst the [Future] provided
      34             :   /// via [Game.onLoad] is not resolved. By default this is an empty Container().
      35             :   final GameLoadingWidgetBuilder? loadingBuilder;
      36             : 
      37             :   /// If set, errors during the onLoad method will not be thrown
      38             :   /// but instead this widget will be shown. If not provided, errors are
      39             :   /// propagated up.
      40             :   final GameErrorWidgetBuilder? errorBuilder;
      41             : 
      42             :   /// Builder to provide a widget tree to be built between the game elements and
      43             :   /// the background color provided via [Game.backgroundColor]
      44             :   final WidgetBuilder? backgroundBuilder;
      45             : 
      46             :   /// A map to show widgets overlay.
      47             :   ///
      48             :   /// See also:
      49             :   /// - [new GameWidget]
      50             :   /// - [Game.overlays]
      51             :   final Map<String, OverlayWidgetBuilder<T>>? overlayBuilderMap;
      52             : 
      53             :   /// A List of the initially active overlays, this is used only on the first build of the widget.
      54             :   /// To control the overlays that are active use [Game.overlays]
      55             :   ///
      56             :   /// See also:
      57             :   /// - [new GameWidget]
      58             :   /// - [Game.overlays]
      59             :   final List<String>? initialActiveOverlays;
      60             : 
      61             :   /// Renders a [game] in a flutter widget tree.
      62             :   ///
      63             :   /// Ex:
      64             :   /// ```
      65             :   /// ...
      66             :   /// Widget build(BuildContext  context) {
      67             :   ///   return GameWidget(
      68             :   ///     game: MyGameClass(),
      69             :   ///   )
      70             :   /// }
      71             :   /// ...
      72             :   /// ```
      73             :   ///
      74             :   /// It is also possible to render layers of widgets over the game surface with widget subtrees.
      75             :   ///
      76             :   /// To do that a [overlayBuilderMap] should be provided. The visibility of
      77             :   /// these overlays are controlled by [Game.overlays] property
      78             :   ///
      79             :   /// Ex:
      80             :   /// ```
      81             :   /// ...
      82             :   ///
      83             :   /// final game = MyGame();
      84             :   ///
      85             :   /// Widget build(BuildContext  context) {
      86             :   ///   return GameWidget(
      87             :   ///     game: game,
      88             :   ///     overlayBuilderMap: {
      89             :   ///       'PauseMenu': (ctx, game) {
      90             :   ///         return Text('A pause menu');
      91             :   ///       },
      92             :   ///     },
      93             :   ///   )
      94             :   /// }
      95             :   /// ...
      96             :   /// game.overlays.add('PauseMenu');
      97             :   /// ```
      98           8 :   const GameWidget({
      99             :     Key? key,
     100             :     required this.game,
     101             :     this.textDirection,
     102             :     this.loadingBuilder,
     103             :     this.errorBuilder,
     104             :     this.backgroundBuilder,
     105             :     this.overlayBuilderMap,
     106             :     this.initialActiveOverlays,
     107           8 :   }) : super(key: key);
     108             : 
     109             :   /// Renders a [game] in a flutter widget tree alongside widgets overlays.
     110             :   ///
     111             :   /// To use overlays, the game subclass has to be mixed with HasWidgetsOverlay.
     112           8 :   @override
     113           8 :   _GameWidgetState<T> createState() => _GameWidgetState<T>();
     114             : }
     115             : 
     116             : class _GameWidgetState<T extends Game> extends State<GameWidget<T>> {
     117             :   Set<String> initialActiveOverlays = {};
     118             : 
     119             :   Future<void>? _gameLoaderFuture;
     120           8 :   Future<void> get _gameLoaderFutureCache =>
     121          40 :       _gameLoaderFuture ?? (_gameLoaderFuture = widget.game.onLoad());
     122             : 
     123           8 :   @override
     124             :   void initState() {
     125           8 :     super.initState();
     126             : 
     127             :     // Add the initial overlays
     128           8 :     _initActiveOverlays();
     129             : 
     130          24 :     addOverlaysListener(widget.game);
     131             :   }
     132             : 
     133           8 :   void _initActiveOverlays() {
     134          16 :     if (widget.initialActiveOverlays == null) {
     135             :       return;
     136             :     }
     137           0 :     _checkOverlays(widget.initialActiveOverlays!.toSet());
     138           0 :     widget.initialActiveOverlays!.forEach((key) {
     139           0 :       widget.game.overlays.add(key);
     140             :     });
     141             :   }
     142             : 
     143           0 :   @override
     144             :   void didUpdateWidget(GameWidget<T> oldWidget) {
     145           0 :     super.didUpdateWidget(oldWidget);
     146           0 :     if (oldWidget.game != widget.game) {
     147           0 :       removeOverlaysListener(oldWidget.game);
     148             : 
     149             :       // Reset the overlays
     150           0 :       _initActiveOverlays();
     151           0 :       addOverlaysListener(widget.game);
     152             : 
     153             :       // Reset the loader future
     154           0 :       _gameLoaderFuture = null;
     155             :     }
     156             :   }
     157             : 
     158           8 :   @override
     159             :   void dispose() {
     160           8 :     super.dispose();
     161          24 :     removeOverlaysListener(widget.game);
     162             :   }
     163             : 
     164             :   // widget overlay stuff
     165           8 :   void addOverlaysListener(T game) {
     166          40 :     widget.game.overlays.addListener(onChangeActiveOverlays);
     167          40 :     initialActiveOverlays = widget.game.overlays.value;
     168             :   }
     169             : 
     170           8 :   void removeOverlaysListener(T game) {
     171          24 :     game.overlays.removeListener(onChangeActiveOverlays);
     172             :   }
     173             : 
     174           0 :   void _checkOverlays(Set<String> overlays) {
     175           0 :     overlays.forEach((overlayKey) {
     176             :       assert(
     177           0 :         widget.overlayBuilderMap?.containsKey(overlayKey) ?? false,
     178           0 :         'A non mapped overlay has been added: $overlayKey',
     179             :       );
     180             :     });
     181             :   }
     182             : 
     183           0 :   void onChangeActiveOverlays() {
     184           0 :     _checkOverlays(widget.game.overlays.value);
     185           0 :     setState(() {
     186           0 :       initialActiveOverlays = widget.game.overlays.value;
     187             :     });
     188             :   }
     189             : 
     190           8 :   @override
     191             :   Widget build(BuildContext context) {
     192          24 :     Widget internalGameWidget = _GameRenderObjectWidget(widget.game);
     193             : 
     194          24 :     final hasBasicDetectors = hasBasicGestureDetectors(widget.game);
     195          24 :     final hasAdvancedDetectors = hasAdvancedGesturesDetectors(widget.game);
     196             : 
     197             :     assert(
     198           0 :       !(hasBasicDetectors && hasAdvancedDetectors),
     199             :       '''
     200             :         WARNING: Both Advanced and Basic detectors detected.
     201             :         Advanced detectors will override basic detectors and the later will not receive events
     202             :       ''',
     203             :     );
     204             : 
     205             :     if (hasBasicDetectors) {
     206           0 :       internalGameWidget = applyBasicGesturesDetectors(
     207           0 :         widget.game,
     208             :         internalGameWidget,
     209             :       );
     210             :     } else if (hasAdvancedDetectors) {
     211           1 :       internalGameWidget = applyAdvancedGesturesDetectors(
     212           2 :         widget.game,
     213             :         internalGameWidget,
     214             :       );
     215             :     }
     216             : 
     217          24 :     if (hasMouseDetectors(widget.game)) {
     218           0 :       internalGameWidget = applyMouseDetectors(
     219           0 :         widget.game,
     220             :         internalGameWidget,
     221             :       );
     222             :     }
     223             : 
     224           8 :     final stackedWidgets = [internalGameWidget];
     225           8 :     _addBackground(context, stackedWidgets);
     226           8 :     _addOverlays(context, stackedWidgets);
     227             : 
     228             :     // We can use Directionality.maybeOf when that method lands on stable
     229          16 :     final textDir = widget.textDirection ?? TextDirection.ltr;
     230             : 
     231           8 :     return Directionality(
     232             :       textDirection: textDir,
     233           8 :       child: Container(
     234          24 :         color: widget.game.backgroundColor(),
     235           8 :         child: LayoutBuilder(
     236           8 :           builder: (_, BoxConstraints constraints) {
     237          40 :             widget.game.onResize(constraints.biggest.toVector2());
     238           8 :             return FutureBuilder(
     239           8 :               future: _gameLoaderFutureCache,
     240           8 :               builder: (_, snapshot) {
     241           8 :                 if (snapshot.hasError) {
     242           0 :                   if (widget.errorBuilder == null) {
     243           0 :                     throw snapshot.error!;
     244             :                   } else {
     245           0 :                     return widget.errorBuilder!(context, snapshot.error!);
     246             :                   }
     247             :                 }
     248          16 :                 if (snapshot.connectionState == ConnectionState.done) {
     249           1 :                   return Stack(children: stackedWidgets);
     250             :                 }
     251          24 :                 return widget.loadingBuilder?.call(context) ?? Container();
     252             :               },
     253             :             );
     254             :           },
     255             :         ),
     256             :       ),
     257             :     );
     258             :   }
     259             : 
     260           8 :   List<Widget> _addBackground(BuildContext context, List<Widget> stackWidgets) {
     261          16 :     if (widget.backgroundBuilder == null) {
     262             :       return stackWidgets;
     263             :     }
     264           0 :     final backgroundContent = KeyedSubtree(
     265           0 :       key: ValueKey(widget.game),
     266           0 :       child: widget.backgroundBuilder!(context),
     267             :     );
     268           0 :     stackWidgets.insert(0, backgroundContent);
     269             :     return stackWidgets;
     270             :   }
     271             : 
     272           8 :   List<Widget> _addOverlays(BuildContext context, List<Widget> stackWidgets) {
     273          16 :     if (widget.overlayBuilderMap == null) {
     274             :       return stackWidgets;
     275             :     }
     276           0 :     final widgets = initialActiveOverlays.map((String overlayKey) {
     277           0 :       final builder = widget.overlayBuilderMap![overlayKey]!;
     278           0 :       return KeyedSubtree(
     279           0 :         key: ValueKey(overlayKey),
     280           0 :         child: builder(context, widget.game),
     281             :       );
     282             :     });
     283           0 :     stackWidgets.addAll(widgets);
     284             :     return stackWidgets;
     285             :   }
     286             : }
     287             : 
     288             : class _GameRenderObjectWidget extends LeafRenderObjectWidget {
     289             :   final Game game;
     290             : 
     291           8 :   const _GameRenderObjectWidget(this.game);
     292             : 
     293           1 :   @override
     294             :   RenderBox createRenderObject(BuildContext context) {
     295           2 :     return GameRenderBox(context, game);
     296             :   }
     297             : 
     298           0 :   @override
     299             :   void updateRenderObject(BuildContext context, GameRenderBox renderObject) {
     300           0 :     renderObject.game = game;
     301             :   }
     302             : }

Generated by: LCOV version 1.15