Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow people to opt-out on repaint boundary #2341

Merged
merged 5 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions packages/flame/lib/src/game/game_render_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,34 @@ import 'package:flutter/widgets.dart' hide WidgetBuilder;
/// render the game.
class RenderGameWidget extends LeafRenderObjectWidget {
final Game game;
final bool addRepaintBoundary;

const RenderGameWidget({
super.key,
required this.game,
required this.addRepaintBoundary,
});

@override
RenderBox createRenderObject(BuildContext context) {
return GameRenderBox(game, context);
return GameRenderBox(game, context, addRepaintBoundary);
}

@override
void updateRenderObject(BuildContext context, GameRenderBox renderObject) {
renderObject
..game = game
..buildContext = context;
..buildContext = context
..isRepaintBoundary = addRepaintBoundary;
}
}

class GameRenderBox extends RenderBox with WidgetsBindingObserver {
GameRenderBox(this._game, this.buildContext);
GameRenderBox(
this._game,
this.buildContext,
this._isRepaintBoundary,
);

GameLoop? gameLoop;

Expand All @@ -56,8 +63,18 @@ class GameRenderBox extends RenderBox with WidgetsBindingObserver {
}
}

bool _isRepaintBoundary = false;

set isRepaintBoundary(bool value) {
if (_isRepaintBoundary == value) {
return;
}
_isRepaintBoundary = value;
markNeedsCompositingBitsUpdate();
}

@override
bool get isRepaintBoundary => true;
bool get isRepaintBoundary => _isRepaintBoundary;
spydon marked this conversation as resolved.
Show resolved Hide resolved

@override
bool get sizedByParent => true;
Expand Down
8 changes: 8 additions & 0 deletions packages/flame/lib/src/game/game_widget/game_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ class GameWidget<T extends Game> extends StatefulWidget {
final MouseCursor? mouseCursor;
final List<String>? initialActiveOverlays;

/// Whether the game should assume the behavior of a [RepaintBoundary].
///
/// Defaults to `true`.
Comment on lines +74 to +76
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these the docs mentioned in the commit? 😄
We should probably add a short paragraph about it in the real docs too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find a proper place to add it on the MD files. Also, we don't doc most of the features of the game widget anywhere besides the dart docs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be fixed in #2344

final bool addRepaintBoundary;

/// Renders a [game] in a flutter widget tree.
///
/// Ex:
Expand Down Expand Up @@ -129,6 +134,7 @@ class GameWidget<T extends Game> extends StatefulWidget {
this.focusNode,
this.autofocus = true,
this.mouseCursor,
this.addRepaintBoundary = true,
}) : gameFactory = null {
_initializeGame(game!);
}
Expand Down Expand Up @@ -165,6 +171,7 @@ class GameWidget<T extends Game> extends StatefulWidget {
this.focusNode,
this.autofocus = true,
this.mouseCursor,
this.addRepaintBoundary = true,
}) : game = null;

/// Renders a [game] in a flutter widget tree alongside widgets overlays.
Expand Down Expand Up @@ -314,6 +321,7 @@ class _GameWidgetState<T extends Game> extends State<GameWidget<T>> {
return _protectedBuild(() {
Widget? internalGameWidget = RenderGameWidget(
game: currentGame,
addRepaintBoundary: widget.addRepaintBoundary,
);

assert(
Expand Down
2 changes: 1 addition & 1 deletion packages/flame/test/game/flame_game_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ void main() {
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
renderBox = GameRenderBox(game, context);
renderBox = GameRenderBox(game, context, true);
return GameWidget(game: game);
},
),
Expand Down
72 changes: 72 additions & 0 deletions packages/flame/test/game/game_render_box_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:flame/game.dart';
import 'package:flame/src/game/game_render_box.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

class _MockFlameGame extends Mock implements FlameGame {}

class _MockBuildContext extends Mock implements BuildContext {}

class _MockPipelineOwner extends Mock implements PipelineOwner {}

final nodesNeedingCompositingBitsUpdate = <RenderObject>[];

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

setUp(nodesNeedingCompositingBitsUpdate.clear);

group('GameRenderBox', () {
test('game/attach', () {
final owner = _MockPipelineOwner();
final game = _MockFlameGame();
when(() => game.paused).thenReturn(true);

final BuildContext context = _MockBuildContext();
final renderBox = GameRenderBox(game, context, false);
renderBox.attach(owner);

verify(() => game.attach(owner, renderBox)).called(1);
expect(renderBox.gameLoop, isNotNull);
verify(() => game.paused).called(1);

final anotherGame = _MockFlameGame();
when(() => anotherGame.paused).thenReturn(true);

renderBox.game = anotherGame;
verify(game.detach).called(1);
verify(() => anotherGame.attach(owner, renderBox)).called(1);
verify(() => anotherGame.paused).called(1);
expect(renderBox.gameLoop, isNotNull);
});
test('buildContext', () {
final owner = _MockPipelineOwner();
final game = _MockFlameGame();
when(() => game.paused).thenReturn(true);

final BuildContext context = _MockBuildContext();
final renderBox = GameRenderBox(game, context, false);
renderBox.attach(owner);

expect(renderBox.buildContext, context);
});
test('isRepaintBoundary', () {
final owner = PipelineOwner();

final game = _MockFlameGame();
when(() => game.paused).thenReturn(true);

final BuildContext context = _MockBuildContext();
final renderBox = GameRenderBox(game, context, false);
renderBox.attach(owner);

expect(renderBox.isRepaintBoundary, false);

renderBox.isRepaintBoundary = true;

expect(renderBox.isRepaintBoundary, true);
});
});
}